summaryrefslogtreecommitdiff
path: root/drivers/base
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/core.c130
-rw-r--r--drivers/base/cpu.c101
-rw-r--r--drivers/base/memory.c114
-rw-r--r--drivers/base/platform.c1
-rw-r--r--drivers/base/power/domain.c1
-rw-r--r--drivers/base/power/generic_ops.c23
-rw-r--r--drivers/base/power/opp.c4
-rw-r--r--drivers/base/power/qos.c6
-rw-r--r--drivers/base/power/runtime.c12
-rw-r--r--drivers/base/power/wakeup.c9
10 files changed, 267 insertions, 134 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 6fdc53d46fa0..dc3ea237f086 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -403,6 +403,36 @@ static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
static struct device_attribute uevent_attr =
__ATTR(uevent, S_IRUGO | S_IWUSR, show_uevent, store_uevent);
+static ssize_t show_online(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ bool val;
+
+ lock_device_hotplug();
+ val = !dev->offline;
+ unlock_device_hotplug();
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t store_online(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ bool val;
+ int ret;
+
+ ret = strtobool(buf, &val);
+ if (ret < 0)
+ return ret;
+
+ lock_device_hotplug();
+ ret = val ? device_online(dev) : device_offline(dev);
+ unlock_device_hotplug();
+ return ret < 0 ? ret : count;
+}
+
+static struct device_attribute online_attr =
+ __ATTR(online, S_IRUGO | S_IWUSR, show_online, store_online);
+
static int device_add_attributes(struct device *dev,
struct device_attribute *attrs)
{
@@ -516,6 +546,12 @@ static int device_add_attrs(struct device *dev)
if (error)
goto err_remove_type_groups;
+ if (device_supports_offline(dev) && !dev->offline_disabled) {
+ error = device_create_file(dev, &online_attr);
+ if (error)
+ goto err_remove_type_groups;
+ }
+
return 0;
err_remove_type_groups:
@@ -536,6 +572,7 @@ static void device_remove_attrs(struct device *dev)
struct class *class = dev->class;
const struct device_type *type = dev->type;
+ device_remove_file(dev, &online_attr);
device_remove_groups(dev, dev->groups);
if (type)
@@ -1435,6 +1472,99 @@ EXPORT_SYMBOL_GPL(put_device);
EXPORT_SYMBOL_GPL(device_create_file);
EXPORT_SYMBOL_GPL(device_remove_file);
+static DEFINE_MUTEX(device_hotplug_lock);
+
+void lock_device_hotplug(void)
+{
+ mutex_lock(&device_hotplug_lock);
+}
+
+void unlock_device_hotplug(void)
+{
+ mutex_unlock(&device_hotplug_lock);
+}
+
+static int device_check_offline(struct device *dev, void *not_used)
+{
+ int ret;
+
+ ret = device_for_each_child(dev, NULL, device_check_offline);
+ if (ret)
+ return ret;
+
+ return device_supports_offline(dev) && !dev->offline ? -EBUSY : 0;
+}
+
+/**
+ * device_offline - Prepare the device for hot-removal.
+ * @dev: Device to be put offline.
+ *
+ * Execute the device bus type's .offline() callback, if present, to prepare
+ * the device for a subsequent hot-removal. If that succeeds, the device must
+ * not be used until either it is removed or its bus type's .online() callback
+ * is executed.
+ *
+ * Call under device_hotplug_lock.
+ */
+int device_offline(struct device *dev)
+{
+ int ret;
+
+ if (dev->offline_disabled)
+ return -EPERM;
+
+ ret = device_for_each_child(dev, NULL, device_check_offline);
+ if (ret)
+ return ret;
+
+ device_lock(dev);
+ if (device_supports_offline(dev)) {
+ if (dev->offline) {
+ ret = 1;
+ } else {
+ ret = dev->bus->offline(dev);
+ if (!ret) {
+ kobject_uevent(&dev->kobj, KOBJ_OFFLINE);
+ dev->offline = true;
+ }
+ }
+ }
+ device_unlock(dev);
+
+ return ret;
+}
+
+/**
+ * device_online - Put the device back online after successful device_offline().
+ * @dev: Device to be put back online.
+ *
+ * If device_offline() has been successfully executed for @dev, but the device
+ * has not been removed subsequently, execute its bus type's .online() callback
+ * to indicate that the device can be used again.
+ *
+ * Call under device_hotplug_lock.
+ */
+int device_online(struct device *dev)
+{
+ int ret = 0;
+
+ device_lock(dev);
+ if (device_supports_offline(dev)) {
+ if (dev->offline) {
+ ret = dev->bus->online(dev);
+ if (!ret) {
+ kobject_uevent(&dev->kobj, KOBJ_ONLINE);
+ dev->offline = false;
+ }
+ } else {
+ ret = 1;
+ }
+ }
+ device_unlock(dev);
+
+ return ret;
+}
+
struct root_device {
struct device dev;
struct module *owner;
diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c
index c377673320ed..a16d20e389f0 100644
--- a/drivers/base/cpu.c
+++ b/drivers/base/cpu.c
@@ -13,17 +13,21 @@
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/percpu.h>
+#include <linux/acpi.h>
#include "base.h"
-struct bus_type cpu_subsys = {
- .name = "cpu",
- .dev_name = "cpu",
-};
-EXPORT_SYMBOL_GPL(cpu_subsys);
-
static DEFINE_PER_CPU(struct device *, cpu_sys_devices);
+static int cpu_subsys_match(struct device *dev, struct device_driver *drv)
+{
+ /* ACPI style match is the only one that may succeed. */
+ if (acpi_driver_match_device(dev, drv))
+ return 1;
+
+ return 0;
+}
+
#ifdef CONFIG_HOTPLUG_CPU
static void change_cpu_under_node(struct cpu *cpu,
unsigned int from_nid, unsigned int to_nid)
@@ -34,65 +38,38 @@ static void change_cpu_under_node(struct cpu *cpu,
cpu->node_id = to_nid;
}
-static ssize_t show_online(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static int __ref cpu_subsys_online(struct device *dev)
{
struct cpu *cpu = container_of(dev, struct cpu, dev);
+ int cpuid = dev->id;
+ int from_nid, to_nid;
+ int ret;
+
+ cpu_hotplug_driver_lock();
- return sprintf(buf, "%u\n", !!cpu_online(cpu->dev.id));
+ from_nid = cpu_to_node(cpuid);
+ ret = cpu_up(cpuid);
+ /*
+ * When hot adding memory to memoryless node and enabling a cpu
+ * on the node, node number of the cpu may internally change.
+ */
+ to_nid = cpu_to_node(cpuid);
+ if (from_nid != to_nid)
+ change_cpu_under_node(cpu, from_nid, to_nid);
+
+ cpu_hotplug_driver_unlock();
+ return ret;
}
-static ssize_t __ref store_online(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static int cpu_subsys_offline(struct device *dev)
{
- struct cpu *cpu = container_of(dev, struct cpu, dev);
- int cpuid = cpu->dev.id;
- int from_nid, to_nid;
- ssize_t ret;
+ int ret;
cpu_hotplug_driver_lock();
- switch (buf[0]) {
- case '0':
- ret = cpu_down(cpuid);
- if (!ret)
- kobject_uevent(&dev->kobj, KOBJ_OFFLINE);
- break;
- case '1':
- from_nid = cpu_to_node(cpuid);
- ret = cpu_up(cpuid);
-
- /*
- * When hot adding memory to memoryless node and enabling a cpu
- * on the node, node number of the cpu may internally change.
- */
- to_nid = cpu_to_node(cpuid);
- if (from_nid != to_nid)
- change_cpu_under_node(cpu, from_nid, to_nid);
-
- if (!ret)
- kobject_uevent(&dev->kobj, KOBJ_ONLINE);
- break;
- default:
- ret = -EINVAL;
- }
+ ret = cpu_down(dev->id);
cpu_hotplug_driver_unlock();
-
- if (ret >= 0)
- ret = count;
return ret;
}
-static DEVICE_ATTR(online, 0644, show_online, store_online);
-
-static struct attribute *hotplug_cpu_attrs[] = {
- &dev_attr_online.attr,
- NULL
-};
-
-static struct attribute_group hotplug_cpu_attr_group = {
- .attrs = hotplug_cpu_attrs,
-};
void unregister_cpu(struct cpu *cpu)
{
@@ -127,6 +104,17 @@ static DEVICE_ATTR(release, S_IWUSR, NULL, cpu_release_store);
#endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */
#endif /* CONFIG_HOTPLUG_CPU */
+struct bus_type cpu_subsys = {
+ .name = "cpu",
+ .dev_name = "cpu",
+ .match = cpu_subsys_match,
+#ifdef CONFIG_HOTPLUG_CPU
+ .online = cpu_subsys_online,
+ .offline = cpu_subsys_offline,
+#endif
+};
+EXPORT_SYMBOL_GPL(cpu_subsys);
+
#ifdef CONFIG_KEXEC
#include <linux/kexec.h>
@@ -185,9 +173,6 @@ static const struct attribute_group *hotplugable_cpu_attr_groups[] = {
#ifdef CONFIG_KEXEC
&crash_note_cpu_attr_group,
#endif
-#ifdef CONFIG_HOTPLUG_CPU
- &hotplug_cpu_attr_group,
-#endif
NULL
};
@@ -302,6 +287,8 @@ int __cpuinit register_cpu(struct cpu *cpu, int num)
cpu->dev.id = num;
cpu->dev.bus = &cpu_subsys;
cpu->dev.release = cpu_device_release;
+ cpu->dev.offline_disabled = !cpu->hotpluggable;
+ cpu->dev.offline = !cpu_online(num);
#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
cpu->dev.bus->uevent = arch_cpu_uevent;
#endif
diff --git a/drivers/base/memory.c b/drivers/base/memory.c
index e315051cfeeb..2b7813ec6d02 100644
--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -37,9 +37,14 @@ static inline int base_memory_block_id(int section_nr)
return section_nr / sections_per_block;
}
+static int memory_subsys_online(struct device *dev);
+static int memory_subsys_offline(struct device *dev);
+
static struct bus_type memory_subsys = {
.name = MEMORY_CLASS_NAME,
.dev_name = MEMORY_CLASS_NAME,
+ .online = memory_subsys_online,
+ .offline = memory_subsys_offline,
};
static BLOCKING_NOTIFIER_HEAD(memory_chain);
@@ -262,33 +267,64 @@ static int __memory_block_change_state(struct memory_block *mem,
{
int ret = 0;
- if (mem->state != from_state_req) {
- ret = -EINVAL;
- goto out;
- }
+ if (mem->state != from_state_req)
+ return -EINVAL;
if (to_state == MEM_OFFLINE)
mem->state = MEM_GOING_OFFLINE;
ret = memory_block_action(mem->start_section_nr, to_state, online_type);
+ mem->state = ret ? from_state_req : to_state;
+ return ret;
+}
- if (ret) {
- mem->state = from_state_req;
- goto out;
- }
+static int memory_subsys_online(struct device *dev)
+{
+ struct memory_block *mem = container_of(dev, struct memory_block, dev);
+ int ret;
- mem->state = to_state;
- switch (mem->state) {
- case MEM_OFFLINE:
- kobject_uevent(&mem->dev.kobj, KOBJ_OFFLINE);
- break;
- case MEM_ONLINE:
- kobject_uevent(&mem->dev.kobj, KOBJ_ONLINE);
- break;
- default:
- break;
+ mutex_lock(&mem->state_mutex);
+
+ ret = mem->state == MEM_ONLINE ? 0 :
+ __memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE,
+ ONLINE_KEEP);
+
+ mutex_unlock(&mem->state_mutex);
+ return ret;
+}
+
+static int memory_subsys_offline(struct device *dev)
+{
+ struct memory_block *mem = container_of(dev, struct memory_block, dev);
+ int ret;
+
+ mutex_lock(&mem->state_mutex);
+
+ ret = mem->state == MEM_OFFLINE ? 0 :
+ __memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE, -1);
+
+ mutex_unlock(&mem->state_mutex);
+ return ret;
+}
+
+static int __memory_block_change_state_uevent(struct memory_block *mem,
+ unsigned long to_state, unsigned long from_state_req,
+ int online_type)
+{
+ int ret = __memory_block_change_state(mem, to_state, from_state_req,
+ online_type);
+ if (!ret) {
+ switch (mem->state) {
+ case MEM_OFFLINE:
+ kobject_uevent(&mem->dev.kobj, KOBJ_OFFLINE);
+ break;
+ case MEM_ONLINE:
+ kobject_uevent(&mem->dev.kobj, KOBJ_ONLINE);
+ break;
+ default:
+ break;
+ }
}
-out:
return ret;
}
@@ -299,8 +335,8 @@ static int memory_block_change_state(struct memory_block *mem,
int ret;
mutex_lock(&mem->state_mutex);
- ret = __memory_block_change_state(mem, to_state, from_state_req,
- online_type);
+ ret = __memory_block_change_state_uevent(mem, to_state, from_state_req,
+ online_type);
mutex_unlock(&mem->state_mutex);
return ret;
@@ -310,22 +346,34 @@ store_mem_state(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct memory_block *mem;
+ bool offline;
int ret = -EINVAL;
mem = container_of(dev, struct memory_block, dev);
- if (!strncmp(buf, "online_kernel", min_t(int, count, 13)))
+ lock_device_hotplug();
+
+ if (!strncmp(buf, "online_kernel", min_t(int, count, 13))) {
+ offline = false;
ret = memory_block_change_state(mem, MEM_ONLINE,
MEM_OFFLINE, ONLINE_KERNEL);
- else if (!strncmp(buf, "online_movable", min_t(int, count, 14)))
+ } else if (!strncmp(buf, "online_movable", min_t(int, count, 14))) {
+ offline = false;
ret = memory_block_change_state(mem, MEM_ONLINE,
MEM_OFFLINE, ONLINE_MOVABLE);
- else if (!strncmp(buf, "online", min_t(int, count, 6)))
+ } else if (!strncmp(buf, "online", min_t(int, count, 6))) {
+ offline = false;
ret = memory_block_change_state(mem, MEM_ONLINE,
MEM_OFFLINE, ONLINE_KEEP);
- else if(!strncmp(buf, "offline", min_t(int, count, 7)))
+ } else if(!strncmp(buf, "offline", min_t(int, count, 7))) {
+ offline = true;
ret = memory_block_change_state(mem, MEM_OFFLINE,
MEM_ONLINE, -1);
+ }
+ if (!ret)
+ dev->offline = offline;
+
+ unlock_device_hotplug();
if (ret)
return ret;
@@ -523,6 +571,7 @@ int register_memory(struct memory_block *memory)
memory->dev.id = memory->start_section_nr / sections_per_block;
memory->dev.release = memory_block_release;
memory->dev.groups = memory_memblk_attr_groups;
+ memory->dev.offline = memory->state == MEM_OFFLINE;
error = device_register(&memory->dev);
return error;
@@ -646,21 +695,6 @@ int unregister_memory_section(struct mem_section *section)
}
#endif /* CONFIG_MEMORY_HOTREMOVE */
-/*
- * offline one memory block. If the memory block has been offlined, do nothing.
- */
-int offline_memory_block(struct memory_block *mem)
-{
- int ret = 0;
-
- mutex_lock(&mem->state_mutex);
- if (mem->state != MEM_OFFLINE)
- ret = __memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE, -1);
- mutex_unlock(&mem->state_mutex);
-
- return ret;
-}
-
/* return true if the memory block is offlined, otherwise, return false */
bool is_memblock_offlined(struct memory_block *mem)
{
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index ed75cf6ef9c9..6eaa7ab9e4bc 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -890,7 +890,6 @@ int platform_pm_restore(struct device *dev)
static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
- .runtime_idle = pm_generic_runtime_idle,
USE_PLATFORM_PM_SLEEP_OPS
};
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index 7072404c8b6d..bfb8955c406c 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -2143,7 +2143,6 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
genpd->max_off_time_changed = true;
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume;
- genpd->domain.ops.runtime_idle = pm_generic_runtime_idle;
genpd->domain.ops.prepare = pm_genpd_prepare;
genpd->domain.ops.suspend = pm_genpd_suspend;
genpd->domain.ops.suspend_late = pm_genpd_suspend_late;
diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c
index bfd898b8988e..5ee030a864f9 100644
--- a/drivers/base/power/generic_ops.c
+++ b/drivers/base/power/generic_ops.c
@@ -12,29 +12,6 @@
#ifdef CONFIG_PM_RUNTIME
/**
- * pm_generic_runtime_idle - Generic runtime idle callback for subsystems.
- * @dev: Device to handle.
- *
- * If PM operations are defined for the @dev's driver and they include
- * ->runtime_idle(), execute it and return its error code, if nonzero.
- * Otherwise, execute pm_runtime_suspend() for the device and return 0.
- */
-int pm_generic_runtime_idle(struct device *dev)
-{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
-
- if (pm && pm->runtime_idle) {
- int ret = pm->runtime_idle(dev);
- if (ret)
- return ret;
- }
-
- pm_runtime_suspend(dev);
- return 0;
-}
-EXPORT_SYMBOL_GPL(pm_generic_runtime_idle);
-
-/**
* pm_generic_runtime_suspend - Generic runtime suspend callback for subsystems.
* @dev: Device to suspend.
*
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c
index f0077cb8e249..c8ec186303db 100644
--- a/drivers/base/power/opp.c
+++ b/drivers/base/power/opp.c
@@ -648,14 +648,14 @@ int opp_init_cpufreq_table(struct device *dev,
list_for_each_entry(opp, &dev_opp->opp_list, node) {
if (opp->available) {
- freq_table[i].index = i;
+ freq_table[i].driver_data = i;
freq_table[i].frequency = opp->rate / 1000;
i++;
}
}
mutex_unlock(&dev_opp_list_lock);
- freq_table[i].index = i;
+ freq_table[i].driver_data = i;
freq_table[i].frequency = CPUFREQ_TABLE_END;
*table = &freq_table[0];
diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c
index 71671c42ef45..5c1361a9e5dd 100644
--- a/drivers/base/power/qos.c
+++ b/drivers/base/power/qos.c
@@ -42,6 +42,7 @@
#include <linux/export.h>
#include <linux/pm_runtime.h>
#include <linux/err.h>
+#include <trace/events/power.h>
#include "power.h"
@@ -305,6 +306,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
else if (!dev->power.qos)
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;
@@ -349,6 +351,8 @@ static int __dev_pm_qos_update_request(struct dev_pm_qos_request *req,
return -EINVAL;
}
+ trace_dev_pm_qos_update_request(dev_name(req->dev), req->type,
+ new_value);
if (curr_value != new_value)
ret = apply_constraint(req, PM_QOS_UPDATE_REQ, new_value);
@@ -398,6 +402,8 @@ static int __dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
if (IS_ERR_OR_NULL(req->dev->power.qos))
return -ENODEV;
+ trace_dev_pm_qos_remove_request(dev_name(req->dev), req->type,
+ PM_QOS_DEFAULT_VALUE);
ret = apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
memset(req, 0, sizeof(*req));
return ret;
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index ef13ad08afb2..268a35097578 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -293,11 +293,8 @@ static int rpm_idle(struct device *dev, int rpmflags)
/* Pending requests need to be canceled. */
dev->power.request = RPM_REQ_NONE;
- if (dev->power.no_callbacks) {
- /* Assume ->runtime_idle() callback would have suspended. */
- retval = rpm_suspend(dev, rpmflags);
+ if (dev->power.no_callbacks)
goto out;
- }
/* Carry out an asynchronous or a synchronous idle notification. */
if (rpmflags & RPM_ASYNC) {
@@ -306,7 +303,8 @@ static int rpm_idle(struct device *dev, int rpmflags)
dev->power.request_pending = true;
queue_work(pm_wq, &dev->power.work);
}
- goto out;
+ trace_rpm_return_int(dev, _THIS_IP_, 0);
+ return 0;
}
dev->power.idle_notification = true;
@@ -326,14 +324,14 @@ static int rpm_idle(struct device *dev, int rpmflags)
callback = dev->driver->pm->runtime_idle;
if (callback)
- __rpm_callback(callback, dev);
+ retval = __rpm_callback(callback, dev);
dev->power.idle_notification = false;
wake_up_all(&dev->power.wait_queue);
out:
trace_rpm_return_int(dev, _THIS_IP_, retval);
- return retval;
+ return retval ? retval : rpm_suspend(dev, rpmflags);
}
/**
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index 79715e7fa43e..2d56f4113ae7 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -659,7 +659,7 @@ void pm_wakeup_event(struct device *dev, unsigned int msec)
}
EXPORT_SYMBOL_GPL(pm_wakeup_event);
-static void print_active_wakeup_sources(void)
+void pm_print_active_wakeup_sources(void)
{
struct wakeup_source *ws;
int active = 0;
@@ -683,6 +683,7 @@ static void print_active_wakeup_sources(void)
last_activity_ws->name);
rcu_read_unlock();
}
+EXPORT_SYMBOL_GPL(pm_print_active_wakeup_sources);
/**
* pm_wakeup_pending - Check if power transition in progress should be aborted.
@@ -707,8 +708,10 @@ bool pm_wakeup_pending(void)
}
spin_unlock_irqrestore(&events_lock, flags);
- if (ret)
- print_active_wakeup_sources();
+ if (ret) {
+ pr_info("PM: Wakeup pending, aborting suspend\n");
+ pm_print_active_wakeup_sources();
+ }
return ret;
}