kernel_samsung_a53x/drivers/soc/samsung/exynos-cpuhp.c
2024-06-15 16:28:48 -03:00

615 lines
14 KiB
C
Executable file

/*
* Copyright (c) 2018 Samsung Electronics Co., Ltd.
*
* CPU Part
*
* CPU Hotplug driver for Exynos
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cpu.h>
#include <linux/fb.h>
#include <linux/kthread.h>
#include <linux/pm_qos.h>
#include <linux/suspend.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <soc/samsung/debug-snapshot.h>
#include <soc/samsung/exynos-cpuhp.h>
static struct {
/* Control cpu hotplug operation */
bool enabled;
/* flag for suspend */
bool suspended;
/* flag for debug print */
bool debug;
/* list head for requester */
struct list_head req_list;
/* default cpuhp request */
struct cpuhp_request default_req;
/* cpuhp request for sysfs */
struct cpuhp_request sysfs_req;
/* cpuhp control cpus */
struct cpumask available_mask;
/* Synchronizes accesses to refcount and cpumask */
struct mutex lock;
/* requested cpuhp mask */
struct cpumask mask;
/* cpuhp kobject */
struct kobject *kobj;
} cpuhp = {
.lock = __MUTEX_INITIALIZER(cpuhp.lock),
};
static char *available_cpus;
module_param(available_cpus, charp, 0);
static char *setup_cpus;
module_param(setup_cpus, charp, 0);
/******************************************************************************/
/* Helper functions */
/******************************************************************************/
static int cpuhp_do(void);
/*
* Update pm_suspend status.
* During suspend-resume, cpuhp driver is stop
*/
static inline void cpuhp_suspend(bool enable)
{
/* This lock guarantees completion of cpuhp_do() */
mutex_lock(&cpuhp.lock);
cpuhp.suspended = enable;
mutex_unlock(&cpuhp.lock);
}
/*
* Update cpuhp enablestatus.
* cpuhp driver is working when enabled big is TRUE
*/
static inline void cpuhp_enable(bool enable)
{
mutex_lock(&cpuhp.lock);
cpuhp.enabled = enable;
mutex_unlock(&cpuhp.lock);
}
/******************************************************************************/
/* External APIs */
/******************************************************************************/
int exynos_cpuhp_remove_request(struct cpuhp_request *req)
{
mutex_lock(&cpuhp.lock);
req->active = 0;
list_del(&req->list);
mutex_unlock(&cpuhp.lock);
return cpuhp_do();
}
EXPORT_SYMBOL_GPL(exynos_cpuhp_remove_request);
int exynos_cpuhp_add_request(struct cpuhp_request *req)
{
mutex_lock(&cpuhp.lock);
if (req->active) {
pr_info("cpuhp request(%s) is already added\n");
return -EBUSY;
}
list_add(&req->list, &cpuhp.req_list);
req->active = 1;
cpumask_clear(&req->mask);
pr_info("Register new cpuhp request (name=%s)\n", req->name);
mutex_unlock(&cpuhp.lock);
return 0;
}
EXPORT_SYMBOL_GPL(exynos_cpuhp_add_request);
int exynos_cpuhp_update_request(struct cpuhp_request *req,
const struct cpumask *mask)
{
cpumask_copy(&req->mask, mask);
return cpuhp_do();
}
EXPORT_SYMBOL_GPL(exynos_cpuhp_update_request);
/******************************************************************************/
/* CPUHP handlers */
/******************************************************************************/
/* legacy hotplug in */
static int cpuhp_in(const struct cpumask *mask)
{
int cpu, ret = 0;
for_each_cpu(cpu, mask) {
ret = add_cpu(cpu);
if (ret < 0) {
/*
* If it fails to enable cpu,
* it cancels cpu hotplug request and retries later.
*/
pr_err("Failed to hotplug in CPU%d with error %d\n",
cpu, ret);
continue;
}
}
return ret < 0 ? ret : 0;
}
/* legacy hotplug out */
static int cpuhp_out(const struct cpumask *mask)
{
int cpu, ret = 0;
/*
* Reverse order of cpu,
* explore cpu7, cpu6, cpu5, ... cpu1
*/
for (cpu = nr_cpu_ids - 1; cpu > 0; cpu--) {
if (!cpumask_test_cpu(cpu, mask))
continue;
ret = remove_cpu(cpu);
if (ret < 0) {
pr_err("Failed to hotplug out CPU%d with error %d\n",
cpu, ret);
continue;
}
}
return ret < 0 ? ret : 0;
}
static struct cpumask cpuhp_get_new_mask(void)
{
struct cpumask mask;
struct cpuhp_request *req;
cpumask_copy(&mask, &cpuhp.available_mask);
list_for_each_entry(req, &cpuhp.req_list, list)
cpumask_and(&mask, &mask, &req->mask);
if (cpumask_empty(&mask))
pr_warn("Requsted cpuhp mask is empty\n");
return mask;
}
/*
* Executes cpu_up
*/
static int cpuhp_cpu_up(struct cpumask *enable_cpus)
{
if (cpumask_empty(enable_cpus))
return 0;
return cpuhp_in(enable_cpus);
}
/*
* Executes cpu_down
*/
static int cpuhp_cpu_down(struct cpumask *disable_cpus)
{
if (cpumask_empty(disable_cpus))
return 0;
return cpuhp_out(disable_cpus);
}
/* print cpu control informatoin for deubgging */
static void cpuhp_print_debug_info(struct cpumask *new_mask)
{
char new_buf[10], cur_buf[10];
scnprintf(cur_buf, sizeof(cur_buf), "%*pbl", cpumask_pr_args(&cpuhp.mask));
scnprintf(new_buf, sizeof(new_buf), "%*pbl", cpumask_pr_args(new_mask));
dbg_snapshot_printk("%s: %s -> %s\n", __func__, cur_buf, new_buf);
/* print cpu control information */
if (cpuhp.debug)
pr_info("%s: %s -> %s\n", __func__, cur_buf, new_buf);
}
static int __cpuhp_do(struct cpumask *req_mask)
{
struct cpumask incoming_cpus, outgoing_cpus;
int ret = 0;
/* get the new online cpus mask */
cpumask_andnot(&incoming_cpus, req_mask, &cpuhp.mask);
/* get the new offline cpus mask */
cpumask_andnot(&outgoing_cpus, &cpuhp.mask, req_mask);
if (!cpumask_empty(&incoming_cpus))
ret = cpuhp_cpu_up(&incoming_cpus);
if (ret)
return ret;
if (!cpumask_empty(&outgoing_cpus))
ret = cpuhp_cpu_down(&outgoing_cpus);
return ret;
}
/*
* cpuhp_do() is the main function for cpu hotplug. Only this function
* enables or disables cpus, so all APIs in this driver call cpuhp_do()
* eventually.
*/
static int cpuhp_do(void)
{
struct cpumask new_mask;
int ret = 0;
mutex_lock(&cpuhp.lock);
/*
* If cpu hotplug is disabled or suspended,
* cpuhp_do() do nothing.
*/
if (!cpuhp.enabled || cpuhp.suspended) {
mutex_unlock(&cpuhp.lock);
return 0;
}
new_mask = cpuhp_get_new_mask();
cpuhp_print_debug_info(&new_mask);
/* if there is no mask change, skip */
if (cpumask_empty(&new_mask) ||
cpumask_equal(&cpuhp.mask, &new_mask))
goto out;
ret = __cpuhp_do(&new_mask);
if (ret)
goto out;
cpumask_and(&cpuhp.mask, &new_mask, cpu_online_mask);
out:
mutex_unlock(&cpuhp.lock);
return ret;
}
static int cpuhp_control(bool enable)
{
struct cpumask mask;
int ret = 0;
if (enable) {
cpuhp_enable(true);
cpuhp_do();
} else {
mutex_lock(&cpuhp.lock);
cpumask_copy(&mask, cpu_possible_mask);
cpumask_andnot(&mask, &mask, cpu_online_mask);
/*
* If it success to enable all CPUs, clear cpuhp.enabled flag.
* Since then all hotplug requests are ignored.
*/
ret = cpuhp_in(&mask);
if (!ret) {
/*
* In this position, can't use cpuhp_enable()
* because already taken cpuhp.lock
*/
cpuhp.enabled = false;
} else {
pr_err("Failed to disable cpu hotplug, please try again\n");
}
mutex_unlock(&cpuhp.lock);
}
return ret;
}
/******************************************************************************/
/* SYSFS Interface */
/******************************************************************************/
/*
* User can change the number of online cpu by using min_online_cpu and
* max_online_cpu sysfs node. User input minimum and maxinum online cpu
* to this node as below:
*
* #echo mask > /sys/power/cpuhp/set_online_cpu
*/
#define STR_LEN 6
static ssize_t set_online_cpu_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, 30, "set online cpu : 0x%x\n",
*(unsigned int *)cpumask_bits(&cpuhp.sysfs_req.mask));
}
static ssize_t set_online_cpu_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct cpumask online_cpus;
char str[STR_LEN], re_str[STR_LEN];
unsigned int cpumask_value;
if (strlen(buf) >= STR_LEN)
return -EINVAL;
if (!sscanf(buf, "%5s", str))
return -EINVAL;
if (str[0] == '0' && toupper(str[1]) == 'X')
/* Move str pointer to remove "0x" */
cpumask_parse(str + 2, &online_cpus);
else {
if (!sscanf(str, "%d", &cpumask_value))
return -EINVAL;
snprintf(re_str, STR_LEN - 1, "%x", cpumask_value);
cpumask_parse(re_str, &online_cpus);
}
if (!cpumask_test_cpu(0, &online_cpus)) {
pr_warn("wrong format\n");
return -EINVAL;
}
exynos_cpuhp_update_request(&cpuhp.sysfs_req, &online_cpus);
return count;
}
DEVICE_ATTR_RW(set_online_cpu);
/*
* It shows cpuhp request information(name, requesting cpu_mask)
* registered in cpuhp req list
*
* #cat /sys/power/cpuhp/reqs
*/
static ssize_t reqs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cpuhp_request *req;
ssize_t ret = 0;
list_for_each_entry(req, &cpuhp.req_list, list)
ret += scnprintf(&buf[ret], 30, "%-16s: (0x%x)\n",
req->name, *(unsigned int *)cpumask_bits(&req->mask));
ret += snprintf(&buf[ret], 30, "available cpu : 0x%x\n",
*(unsigned int *)cpumask_bits(&cpuhp.available_mask));
ret += snprintf(&buf[ret], 30, "\nrequested cpu: 0x%x\n",
*(unsigned int *)cpumask_bits(&cpuhp.mask));
return ret;
}
DEVICE_ATTR_RO(reqs);
/*
* User can control the cpu hotplug operation as below:
*
* #echo 1 > /sys/power/cpuhp/enabled => enable
* #echo 0 > /sys/power/cpuhp/enabled => disable
*
* If enabled become 0, hotplug driver enable the all cpus and no hotplug
* operation happen from hotplug driver.
*/
static ssize_t enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, 10, "%d\n", cpuhp.enabled);
}
static ssize_t enabled_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int input;
if (!sscanf(buf, "%d", &input))
return -EINVAL;
cpuhp_control(!!input);
return count;
}
DEVICE_ATTR_RW(enabled);
/*
* User can control en/disable debug mode
*
* #echo 1 > /sys/power/cpuhp/debug => enable
* #echo 0 > /sys/power/cpuhp/debug => disable
*
* When it is enabled, information is printed every time there is a cpu control
*/
static ssize_t debug_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, 10, "%d\n", cpuhp.debug);
}
static ssize_t debug_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int input;
if (!sscanf(buf, "%d", &input))
return -EINVAL;
cpuhp.debug = !!input;
return count;
}
DEVICE_ATTR_RW(debug);
static struct attribute *exynos_cpuhp_attrs[] = {
&dev_attr_set_online_cpu.attr,
&dev_attr_reqs.attr,
&dev_attr_enabled.attr,
&dev_attr_debug.attr,
NULL,
};
static struct attribute_group exynos_cpuhp_group = {
.name = "cpuhp",
.attrs = exynos_cpuhp_attrs,
};
/******************************************************************************/
/* Initialize Driver */
/******************************************************************************/
static int parse_cpumask(char *str_cpus, struct cpumask *dest_mask)
{
if (str_cpus[0] == '0' && toupper(str_cpus[1]) == 'X')
/* Move str pointer to remove "0x" */
cpumask_parse(str_cpus + 2, dest_mask);
else
cpumask_parse(str_cpus, dest_mask);
cpumask_and(dest_mask, dest_mask, cpu_possible_mask);
cpumask_and(dest_mask, dest_mask, &cpuhp.available_mask);
if (!cpumask_test_cpu(0, dest_mask)) {
pr_warn("wrong format\n");
return -1;
}
return 0;
}
static void init_setup_cpus(void)
{
struct cpumask setup_mask;
int ret;
ret = parse_cpumask(setup_cpus, &setup_mask);
if (ret)
return;
mutex_lock(&cpuhp.lock);
ret = __cpuhp_do(&setup_mask);
mutex_unlock(&cpuhp.lock);
if (ret)
pr_warn("%s: Failed setup_cpus %*pbl", cpumask_pr_args(&setup_mask));
else
pr_info("%s: bootargs setup_cpus = %s\n", __func__, setup_cpus);
}
static void init_available_cpus(void)
{
struct cpumask mask;
int ret;
cpumask_copy(&cpuhp.available_mask, cpu_possible_mask);
if (!available_cpus)
return;
ret = parse_cpumask(available_cpus, &mask);
if (ret)
return;
cpumask_copy(&cpuhp.available_mask, &mask);
pr_info("%s: bootargs available_cpus = %s\n", __func__, available_cpus);
}
static void cpuhp_init(void)
{
/* init req list */
INIT_LIST_HEAD(&cpuhp.req_list);
/* register default cpuhp request */
strcpy(cpuhp.default_req.name, "cpuhp_default");
exynos_cpuhp_add_request(&cpuhp.default_req);
exynos_cpuhp_update_request(&cpuhp.default_req, cpu_possible_mask);
/* register cpuhp request for sysfs */
strcpy(cpuhp.sysfs_req.name, "cpuhp_sysfs");
exynos_cpuhp_add_request(&cpuhp.sysfs_req);
exynos_cpuhp_update_request(&cpuhp.sysfs_req, cpu_possible_mask);
cpumask_copy(&cpuhp.mask, cpu_online_mask);
init_available_cpus();
cpuhp_enable(true);
if (setup_cpus)
init_setup_cpus();
}
static int exynos_cpuhp_probe(struct platform_device *pdev)
{
/* Create CPUHP sysfs */
if (sysfs_create_group(&pdev->dev.kobj, &exynos_cpuhp_group))
pr_err("Failed to create sysfs for CPUHP\n");
/* Link CPUHP sysfs to /sys/devices/system/cpu/cpuhp */
if (sysfs_create_link(&cpu_subsys.dev_root->kobj,
&pdev->dev.kobj, "cpuhp"))
pr_err("Failed to link CPUHP sysfs to cpuctrl\n");
cpuhp_init();
pr_info("Exynos CPUHP driver probe done!\n");
return 0;
}
static const struct of_device_id of_exynos_cpuhp_match[] = {
{ .compatible = "samsung,exynos-cpuhp", },
{ },
};
MODULE_DEVICE_TABLE(of, of_exynos_cpuhp_match);
static struct platform_driver exynos_cpuhp_driver = {
.driver = {
.name = "exynos-cpuhp",
.owner = THIS_MODULE,
.of_match_table = of_exynos_cpuhp_match,
},
.probe = exynos_cpuhp_probe,
};
static int __init exynos_cpuhp_init(void)
{
return platform_driver_register(&exynos_cpuhp_driver);
}
arch_initcall(exynos_cpuhp_init);
static void __exit exynos_cpuhp_exit(void)
{
platform_driver_unregister(&exynos_cpuhp_driver);
}
module_exit(exynos_cpuhp_exit);
MODULE_DESCRIPTION("Exynos CPUHP driver");
MODULE_LICENSE("GPL");