|
@@ -957,28 +957,67 @@ void cpufreq_sysfs_remove_file(const struct attribute *attr)
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(cpufreq_sysfs_remove_file);
|
|
EXPORT_SYMBOL(cpufreq_sysfs_remove_file);
|
|
|
|
|
|
-/* symlink affected CPUs */
|
|
|
|
|
|
+static int add_cpu_dev_symlink(struct cpufreq_policy *policy, int cpu)
|
|
|
|
+{
|
|
|
|
+ struct device *cpu_dev;
|
|
|
|
+
|
|
|
|
+ pr_debug("%s: Adding symlink for CPU: %u\n", __func__, cpu);
|
|
|
|
+
|
|
|
|
+ if (!policy)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ cpu_dev = get_cpu_device(cpu);
|
|
|
|
+ if (WARN_ON(!cpu_dev))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ return sysfs_create_link(&cpu_dev->kobj, &policy->kobj, "cpufreq");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void remove_cpu_dev_symlink(struct cpufreq_policy *policy, int cpu)
|
|
|
|
+{
|
|
|
|
+ struct device *cpu_dev;
|
|
|
|
+
|
|
|
|
+ pr_debug("%s: Removing symlink for CPU: %u\n", __func__, cpu);
|
|
|
|
+
|
|
|
|
+ cpu_dev = get_cpu_device(cpu);
|
|
|
|
+ if (WARN_ON(!cpu_dev))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ sysfs_remove_link(&cpu_dev->kobj, "cpufreq");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Add/remove symlinks for all related CPUs */
|
|
static int cpufreq_add_dev_symlink(struct cpufreq_policy *policy)
|
|
static int cpufreq_add_dev_symlink(struct cpufreq_policy *policy)
|
|
{
|
|
{
|
|
unsigned int j;
|
|
unsigned int j;
|
|
int ret = 0;
|
|
int ret = 0;
|
|
|
|
|
|
- for_each_cpu(j, policy->cpus) {
|
|
|
|
- struct device *cpu_dev;
|
|
|
|
-
|
|
|
|
|
|
+ /* Some related CPUs might not be present (physically hotplugged) */
|
|
|
|
+ for_each_cpu_and(j, policy->related_cpus, cpu_present_mask) {
|
|
if (j == policy->kobj_cpu)
|
|
if (j == policy->kobj_cpu)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- pr_debug("Adding link for CPU: %u\n", j);
|
|
|
|
- cpu_dev = get_cpu_device(j);
|
|
|
|
- ret = sysfs_create_link(&cpu_dev->kobj, &policy->kobj,
|
|
|
|
- "cpufreq");
|
|
|
|
|
|
+ ret = add_cpu_dev_symlink(policy, j);
|
|
if (ret)
|
|
if (ret)
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void cpufreq_remove_dev_symlink(struct cpufreq_policy *policy)
|
|
|
|
+{
|
|
|
|
+ unsigned int j;
|
|
|
|
+
|
|
|
|
+ /* Some related CPUs might not be present (physically hotplugged) */
|
|
|
|
+ for_each_cpu_and(j, policy->related_cpus, cpu_present_mask) {
|
|
|
|
+ if (j == policy->kobj_cpu)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ remove_cpu_dev_symlink(policy, j);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
static int cpufreq_add_dev_interface(struct cpufreq_policy *policy,
|
|
static int cpufreq_add_dev_interface(struct cpufreq_policy *policy,
|
|
struct device *dev)
|
|
struct device *dev)
|
|
{
|
|
{
|
|
@@ -1075,7 +1114,7 @@ static int cpufreq_add_policy_cpu(struct cpufreq_policy *policy,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- return sysfs_create_link(&dev->kobj, &policy->kobj, "cpufreq");
|
|
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static struct cpufreq_policy *cpufreq_policy_restore(unsigned int cpu)
|
|
static struct cpufreq_policy *cpufreq_policy_restore(unsigned int cpu)
|
|
@@ -1095,7 +1134,7 @@ static struct cpufreq_policy *cpufreq_policy_restore(unsigned int cpu)
|
|
return policy;
|
|
return policy;
|
|
}
|
|
}
|
|
|
|
|
|
-static struct cpufreq_policy *cpufreq_policy_alloc(void)
|
|
|
|
|
|
+static struct cpufreq_policy *cpufreq_policy_alloc(int cpu)
|
|
{
|
|
{
|
|
struct cpufreq_policy *policy;
|
|
struct cpufreq_policy *policy;
|
|
|
|
|
|
@@ -1116,6 +1155,11 @@ static struct cpufreq_policy *cpufreq_policy_alloc(void)
|
|
init_completion(&policy->kobj_unregister);
|
|
init_completion(&policy->kobj_unregister);
|
|
INIT_WORK(&policy->update, handle_update);
|
|
INIT_WORK(&policy->update, handle_update);
|
|
|
|
|
|
|
|
+ policy->cpu = cpu;
|
|
|
|
+
|
|
|
|
+ /* Set this once on allocation */
|
|
|
|
+ policy->kobj_cpu = cpu;
|
|
|
|
+
|
|
return policy;
|
|
return policy;
|
|
|
|
|
|
err_free_cpumask:
|
|
err_free_cpumask:
|
|
@@ -1134,10 +1178,11 @@ static void cpufreq_policy_put_kobj(struct cpufreq_policy *policy)
|
|
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
|
|
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
|
|
CPUFREQ_REMOVE_POLICY, policy);
|
|
CPUFREQ_REMOVE_POLICY, policy);
|
|
|
|
|
|
- down_read(&policy->rwsem);
|
|
|
|
|
|
+ down_write(&policy->rwsem);
|
|
|
|
+ cpufreq_remove_dev_symlink(policy);
|
|
kobj = &policy->kobj;
|
|
kobj = &policy->kobj;
|
|
cmp = &policy->kobj_unregister;
|
|
cmp = &policy->kobj_unregister;
|
|
- up_read(&policy->rwsem);
|
|
|
|
|
|
+ up_write(&policy->rwsem);
|
|
kobject_put(kobj);
|
|
kobject_put(kobj);
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1168,27 +1213,14 @@ static void cpufreq_policy_free(struct cpufreq_policy *policy)
|
|
kfree(policy);
|
|
kfree(policy);
|
|
}
|
|
}
|
|
|
|
|
|
-static int update_policy_cpu(struct cpufreq_policy *policy, unsigned int cpu,
|
|
|
|
- struct device *cpu_dev)
|
|
|
|
|
|
+static void update_policy_cpu(struct cpufreq_policy *policy, unsigned int cpu)
|
|
{
|
|
{
|
|
- int ret;
|
|
|
|
-
|
|
|
|
if (WARN_ON(cpu == policy->cpu))
|
|
if (WARN_ON(cpu == policy->cpu))
|
|
- return 0;
|
|
|
|
-
|
|
|
|
- /* Move kobject to the new policy->cpu */
|
|
|
|
- ret = kobject_move(&policy->kobj, &cpu_dev->kobj);
|
|
|
|
- if (ret) {
|
|
|
|
- pr_err("%s: Failed to move kobj: %d\n", __func__, ret);
|
|
|
|
- return ret;
|
|
|
|
- }
|
|
|
|
|
|
+ return;
|
|
|
|
|
|
down_write(&policy->rwsem);
|
|
down_write(&policy->rwsem);
|
|
policy->cpu = cpu;
|
|
policy->cpu = cpu;
|
|
- policy->kobj_cpu = cpu;
|
|
|
|
up_write(&policy->rwsem);
|
|
up_write(&policy->rwsem);
|
|
-
|
|
|
|
- return 0;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1206,13 +1238,19 @@ static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
|
|
int ret = -ENOMEM;
|
|
int ret = -ENOMEM;
|
|
struct cpufreq_policy *policy;
|
|
struct cpufreq_policy *policy;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
- bool recover_policy = cpufreq_suspended;
|
|
|
|
-
|
|
|
|
- if (cpu_is_offline(cpu))
|
|
|
|
- return 0;
|
|
|
|
|
|
+ bool recover_policy = !sif;
|
|
|
|
|
|
pr_debug("adding CPU %u\n", cpu);
|
|
pr_debug("adding CPU %u\n", cpu);
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Only possible if 'cpu' wasn't physically present earlier and we are
|
|
|
|
+ * here from subsys_interface add callback. A hotplug notifier will
|
|
|
|
+ * follow and we will handle it like logical CPU hotplug then. For now,
|
|
|
|
+ * just create the sysfs link.
|
|
|
|
+ */
|
|
|
|
+ if (cpu_is_offline(cpu))
|
|
|
|
+ return add_cpu_dev_symlink(per_cpu(cpufreq_cpu_data, cpu), cpu);
|
|
|
|
+
|
|
if (!down_read_trylock(&cpufreq_rwsem))
|
|
if (!down_read_trylock(&cpufreq_rwsem))
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
@@ -1232,7 +1270,7 @@ static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
|
|
policy = recover_policy ? cpufreq_policy_restore(cpu) : NULL;
|
|
policy = recover_policy ? cpufreq_policy_restore(cpu) : NULL;
|
|
if (!policy) {
|
|
if (!policy) {
|
|
recover_policy = false;
|
|
recover_policy = false;
|
|
- policy = cpufreq_policy_alloc();
|
|
|
|
|
|
+ policy = cpufreq_policy_alloc(cpu);
|
|
if (!policy)
|
|
if (!policy)
|
|
goto nomem_out;
|
|
goto nomem_out;
|
|
}
|
|
}
|
|
@@ -1243,12 +1281,8 @@ static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
|
|
* the creation of a brand new one. So we need to perform this update
|
|
* the creation of a brand new one. So we need to perform this update
|
|
* by invoking update_policy_cpu().
|
|
* by invoking update_policy_cpu().
|
|
*/
|
|
*/
|
|
- if (recover_policy && cpu != policy->cpu) {
|
|
|
|
- WARN_ON(update_policy_cpu(policy, cpu, dev));
|
|
|
|
- } else {
|
|
|
|
- policy->cpu = cpu;
|
|
|
|
- policy->kobj_cpu = cpu;
|
|
|
|
- }
|
|
|
|
|
|
+ if (recover_policy && cpu != policy->cpu)
|
|
|
|
+ update_policy_cpu(policy, cpu);
|
|
|
|
|
|
cpumask_copy(policy->cpus, cpumask_of(cpu));
|
|
cpumask_copy(policy->cpus, cpumask_of(cpu));
|
|
|
|
|
|
@@ -1427,29 +1461,14 @@ static int __cpufreq_remove_dev_prepare(struct device *dev,
|
|
CPUFREQ_NAME_LEN);
|
|
CPUFREQ_NAME_LEN);
|
|
up_write(&policy->rwsem);
|
|
up_write(&policy->rwsem);
|
|
|
|
|
|
- if (cpu != policy->kobj_cpu) {
|
|
|
|
- sysfs_remove_link(&dev->kobj, "cpufreq");
|
|
|
|
- } else if (cpus > 1) {
|
|
|
|
- /* Nominate new CPU */
|
|
|
|
- int new_cpu = cpumask_any_but(policy->cpus, cpu);
|
|
|
|
- struct device *cpu_dev = get_cpu_device(new_cpu);
|
|
|
|
-
|
|
|
|
- sysfs_remove_link(&cpu_dev->kobj, "cpufreq");
|
|
|
|
- ret = update_policy_cpu(policy, new_cpu, cpu_dev);
|
|
|
|
- if (ret) {
|
|
|
|
- if (sysfs_create_link(&cpu_dev->kobj, &policy->kobj,
|
|
|
|
- "cpufreq"))
|
|
|
|
- pr_err("%s: Failed to restore kobj link to cpu:%d\n",
|
|
|
|
- __func__, cpu_dev->id);
|
|
|
|
- return ret;
|
|
|
|
- }
|
|
|
|
|
|
+ if (cpu != policy->cpu)
|
|
|
|
+ return 0;
|
|
|
|
|
|
- if (!cpufreq_suspended)
|
|
|
|
- pr_debug("%s: policy Kobject moved to cpu: %d from: %d\n",
|
|
|
|
- __func__, new_cpu, cpu);
|
|
|
|
- } else if (cpufreq_driver->stop_cpu) {
|
|
|
|
|
|
+ if (cpus > 1)
|
|
|
|
+ /* Nominate new CPU */
|
|
|
|
+ update_policy_cpu(policy, cpumask_any_but(policy->cpus, cpu));
|
|
|
|
+ else if (cpufreq_driver->stop_cpu)
|
|
cpufreq_driver->stop_cpu(policy);
|
|
cpufreq_driver->stop_cpu(policy);
|
|
- }
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -1470,32 +1489,11 @@ static int __cpufreq_remove_dev_finish(struct device *dev,
|
|
cpumask_clear_cpu(cpu, policy->cpus);
|
|
cpumask_clear_cpu(cpu, policy->cpus);
|
|
up_write(&policy->rwsem);
|
|
up_write(&policy->rwsem);
|
|
|
|
|
|
- /* If cpu is last user of policy, free policy */
|
|
|
|
- if (policy_is_inactive(policy)) {
|
|
|
|
- if (has_target()) {
|
|
|
|
- ret = __cpufreq_governor(policy,
|
|
|
|
- CPUFREQ_GOV_POLICY_EXIT);
|
|
|
|
- if (ret) {
|
|
|
|
- pr_err("%s: Failed to exit governor\n",
|
|
|
|
- __func__);
|
|
|
|
- return ret;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!cpufreq_suspended)
|
|
|
|
- cpufreq_policy_put_kobj(policy);
|
|
|
|
|
|
+ /* Not the last cpu of policy, start governor again ? */
|
|
|
|
+ if (!policy_is_inactive(policy)) {
|
|
|
|
+ if (!has_target())
|
|
|
|
+ return 0;
|
|
|
|
|
|
- /*
|
|
|
|
- * Perform the ->exit() even during light-weight tear-down,
|
|
|
|
- * since this is a core component, and is essential for the
|
|
|
|
- * subsequent light-weight ->init() to succeed.
|
|
|
|
- */
|
|
|
|
- if (cpufreq_driver->exit)
|
|
|
|
- cpufreq_driver->exit(policy);
|
|
|
|
-
|
|
|
|
- if (!cpufreq_suspended)
|
|
|
|
- cpufreq_policy_free(policy);
|
|
|
|
- } else if (has_target()) {
|
|
|
|
ret = __cpufreq_governor(policy, CPUFREQ_GOV_START);
|
|
ret = __cpufreq_governor(policy, CPUFREQ_GOV_START);
|
|
if (!ret)
|
|
if (!ret)
|
|
ret = __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS);
|
|
ret = __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS);
|
|
@@ -1504,8 +1502,34 @@ static int __cpufreq_remove_dev_finish(struct device *dev,
|
|
pr_err("%s: Failed to start governor\n", __func__);
|
|
pr_err("%s: Failed to start governor\n", __func__);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /* If cpu is last user of policy, free policy */
|
|
|
|
+ if (has_target()) {
|
|
|
|
+ ret = __cpufreq_governor(policy, CPUFREQ_GOV_POLICY_EXIT);
|
|
|
|
+ if (ret) {
|
|
|
|
+ pr_err("%s: Failed to exit governor\n", __func__);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Free the policy kobjects only if the driver is getting removed. */
|
|
|
|
+ if (sif)
|
|
|
|
+ cpufreq_policy_put_kobj(policy);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Perform the ->exit() even during light-weight tear-down,
|
|
|
|
+ * since this is a core component, and is essential for the
|
|
|
|
+ * subsequent light-weight ->init() to succeed.
|
|
|
|
+ */
|
|
|
|
+ if (cpufreq_driver->exit)
|
|
|
|
+ cpufreq_driver->exit(policy);
|
|
|
|
+
|
|
|
|
+ if (sif)
|
|
|
|
+ cpufreq_policy_free(policy);
|
|
|
|
+
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1519,8 +1543,34 @@ static int cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif)
|
|
unsigned int cpu = dev->id;
|
|
unsigned int cpu = dev->id;
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
- if (cpu_is_offline(cpu))
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Only possible if 'cpu' is getting physically removed now. A hotplug
|
|
|
|
+ * notifier should have already been called and we just need to remove
|
|
|
|
+ * link or free policy here.
|
|
|
|
+ */
|
|
|
|
+ if (cpu_is_offline(cpu)) {
|
|
|
|
+ struct cpufreq_policy *policy = per_cpu(cpufreq_cpu_data, cpu);
|
|
|
|
+ struct cpumask mask;
|
|
|
|
+
|
|
|
|
+ if (!policy)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ cpumask_copy(&mask, policy->related_cpus);
|
|
|
|
+ cpumask_clear_cpu(cpu, &mask);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Free policy only if all policy->related_cpus are removed
|
|
|
|
+ * physically.
|
|
|
|
+ */
|
|
|
|
+ if (cpumask_intersects(&mask, cpu_present_mask)) {
|
|
|
|
+ remove_cpu_dev_symlink(policy, cpu);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ cpufreq_policy_put_kobj(policy);
|
|
|
|
+ cpufreq_policy_free(policy);
|
|
return 0;
|
|
return 0;
|
|
|
|
+ }
|
|
|
|
|
|
ret = __cpufreq_remove_dev_prepare(dev, sif);
|
|
ret = __cpufreq_remove_dev_prepare(dev, sif);
|
|
|
|
|