/* * Copyright (c) 2020 samsung electronics co., ltd. * http://www.samsung.com/ * * Author : Choonghoon Park (choong.park@samsung.com) * * 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. * * Exynos CPU Frequency QoS tracing driver implementation */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include struct freq_qos_tracer_request { struct list_head node; char *func; unsigned int line; struct freq_qos_request *req; }; struct freq_qos_tracer_domain { struct list_head node; struct list_head min_requests; struct list_head max_requests; struct freq_constraints *constraints; struct kobject kobj; }; /* * list head of cpu freq constraints */ static LIST_HEAD(domains); static DEFINE_SPINLOCK(freq_qos_trace_lock); /********************************************************************* * Helper functions * *********************************************************************/ static struct freq_qos_tracer_domain * find_freq_qos_domain(struct freq_constraints *f_const) { struct freq_qos_tracer_domain *domain; list_for_each_entry(domain, &domains, node) { if (f_const == domain->constraints) return domain; } return NULL; } static struct freq_qos_tracer_request * find_freq_qos_req(struct freq_qos_tracer_domain *domain, struct freq_qos_request *f_req) { struct freq_qos_tracer_request *qos_req; struct list_head *qos_req_list; if (f_req->type == FREQ_QOS_MIN) qos_req_list = &domain->min_requests; else qos_req_list = &domain->max_requests; list_for_each_entry(qos_req, qos_req_list, node) { if (qos_req->req == f_req) return qos_req; } return NULL; } /********************************************************************* * External APIs * *********************************************************************/ int __freq_qos_tracer_add_request(char *func, unsigned int line, struct freq_constraints *f_const, struct freq_qos_request *f_req, enum freq_qos_req_type type, s32 value) { int ret = 0; struct freq_qos_tracer_request *qos_req; struct freq_qos_tracer_domain *domain; ret = freq_qos_add_request(f_const, f_req, type, value); if (ret < 0) return ret; qos_req = kzalloc(sizeof(*qos_req), GFP_KERNEL); if (!qos_req) { ret = -ENOMEM; return ret; } qos_req->func = func; qos_req->line = line; qos_req->req = f_req; INIT_LIST_HEAD(&qos_req->node); domain = find_freq_qos_domain(f_const); if (!domain) { ret = -ENODEV; kfree(qos_req); return ret; } spin_lock(&freq_qos_trace_lock); if (type == FREQ_QOS_MIN) list_add(&qos_req->node, &domain->min_requests); else list_add(&qos_req->node, &domain->max_requests); spin_unlock(&freq_qos_trace_lock); return ret; } EXPORT_SYMBOL_GPL(__freq_qos_tracer_add_request); int freq_qos_tracer_remove_request(struct freq_qos_request *req) { int ret = 0; struct freq_qos_tracer_domain *domain; struct freq_qos_tracer_request *qos_req; spin_lock(&freq_qos_trace_lock); domain = find_freq_qos_domain(req->qos); if (!domain) { ret = -ENODEV; goto unlock; } qos_req = find_freq_qos_req(domain, req); if (!qos_req) { ret = -ENODEV; goto unlock; } list_del(&qos_req->node); kfree(qos_req); spin_unlock(&freq_qos_trace_lock); freq_qos_remove_request(req); return 0; unlock: spin_unlock(&freq_qos_trace_lock); return ret; } EXPORT_SYMBOL_GPL(freq_qos_tracer_remove_request); /********************************************************************* * Sysfs support * *********************************************************************/ struct freq_qos_tracer_attr { struct attribute attr; ssize_t (*show)(struct kobject *, char *); ssize_t (*store)(struct kobject *, const char *, size_t count); }; #define to_freq_qos_tracer_dom(k) \ container_of(k, struct freq_qos_tracer_domain, kobj) #define FREQ_QOS_TRACER_ATTR_RW(name) \ static struct freq_qos_tracer_attr freq_qos_tracer_attr_##name = \ __ATTR(name, 0644, freq_qos_tracer_##name##_show, \ freq_qos_tracer_##name##_store) #define FREQ_QOS_TRACER_ATTR_RO(name) \ static struct freq_qos_tracer_attr freq_qos_tracer_attr_##name = \ __ATTR(name, 0444, freq_qos_tracer_##name##_show, \ NULL) static ssize_t freq_qos_tracer_limit_show(struct kobject *kobj, char *buf, enum freq_qos_req_type type) { int ret = 0; int tot_reqs = 0, active_reqs = 0; struct freq_qos_tracer_domain *domain = to_freq_qos_tracer_dom(kobj); struct freq_qos_tracer_request *qos_req; struct list_head *qos_req_list; s32 val; char *type_str; if (type == FREQ_QOS_MIN) { qos_req_list = &domain->min_requests; val = 0; type_str = "Minimum"; } else { qos_req_list = &domain->max_requests; val = INT_MAX; type_str = "Maximum"; } spin_lock(&freq_qos_trace_lock); if (list_empty(qos_req_list)) { snprintf(buf, PAGE_SIZE, "Empty!\n"); goto print_total; } list_for_each_entry(qos_req, qos_req_list, node) { char *state = "Default"; if (freq_qos_request_active(qos_req->req)) { active_reqs++; state = "Active"; if (type == FREQ_QOS_MIN) val = max((qos_req->req->pnode).prio, val); else val = min((qos_req->req->pnode).prio, val); } tot_reqs++; ret += snprintf(buf + ret, PAGE_SIZE, "%d: %d: %s(%s:%d)\n", tot_reqs, (qos_req->req->pnode).prio, state, qos_req->func, qos_req->line); } print_total: ret += snprintf(buf + ret, PAGE_SIZE, "Type=%s, Value=%d, Requests: actvie=%d / total=%d\n", type_str, val, active_reqs, tot_reqs); spin_unlock(&freq_qos_trace_lock); return ret; } static ssize_t freq_qos_tracer_min_limit_show(struct kobject *kobj, char *buf) { return freq_qos_tracer_limit_show(kobj, buf, FREQ_QOS_MIN); } static ssize_t freq_qos_tracer_max_limit_show(struct kobject *kobj, char *buf) { return freq_qos_tracer_limit_show(kobj, buf, FREQ_QOS_MAX); } FREQ_QOS_TRACER_ATTR_RO(min_limit); FREQ_QOS_TRACER_ATTR_RO(max_limit); static struct attribute *freq_qos_tracer_attrs[] = { &freq_qos_tracer_attr_min_limit.attr, &freq_qos_tracer_attr_max_limit.attr, NULL, }; static ssize_t show(struct kobject *kobj, struct attribute *at, char *buf) { struct freq_qos_tracer_attr *fvattr = container_of(at, struct freq_qos_tracer_attr, attr); return fvattr->show(kobj, buf); } static ssize_t store(struct kobject *kobj, struct attribute *at, const char *buf, size_t count) { struct freq_qos_tracer_attr *fvattr = container_of(at, struct freq_qos_tracer_attr, attr); return fvattr->store(kobj, buf, count); } static const struct sysfs_ops freq_qos_tracer_sysfs_ops = { .show = show, .store = store, }; static struct kobj_type ktype_freq_qos_tracer = { .sysfs_ops = &freq_qos_tracer_sysfs_ops, .default_attrs = freq_qos_tracer_attrs, }; /********************************************************************* * Initialization * *********************************************************************/ static cpumask_var_t cpus_to_visit; static struct kobject *pdev_kobj; static struct notifier_block freq_qos_tracer_notifier; static void init_constraint_done_workfn(struct work_struct *work) { cpufreq_unregister_notifier(&freq_qos_tracer_notifier, CPUFREQ_POLICY_NOTIFIER); free_cpumask_var(cpus_to_visit); pdev_kobj = NULL; } static DECLARE_WORK(init_constraint_done_work, init_constraint_done_workfn); static int freq_qos_tracer_callback(struct notifier_block *nb, unsigned long val, void *data) { struct cpufreq_policy *policy = data; struct freq_qos_tracer_domain *domain; if (val != CPUFREQ_CREATE_POLICY) return 0; domain = kzalloc(sizeof(*domain), GFP_KERNEL); if (!domain) return -ENOMEM; INIT_LIST_HEAD(&domain->min_requests); INIT_LIST_HEAD(&domain->max_requests); INIT_LIST_HEAD(&domain->node); domain->constraints = &policy->constraints; list_add(&domain->node, &domains); if (kobject_init_and_add(&domain->kobj, &ktype_freq_qos_tracer, pdev_kobj, "domain%d", policy->cpu)) pr_err("Failed to init exynos freq QoS sysfs " "of policy%d\n", policy->cpu); cpumask_andnot(cpus_to_visit, cpus_to_visit, policy->related_cpus); if (cpumask_empty(cpus_to_visit)) { pr_info("init CPU freq constraint done\n"); schedule_work(&init_constraint_done_work); } return 0; } static struct notifier_block freq_qos_tracer_notifier = { .notifier_call = freq_qos_tracer_callback, .priority = INT_MIN, }; static int freq_qos_tracer_probe(struct platform_device *pdev) { int ret = 0; if (!alloc_cpumask_var(&cpus_to_visit, GFP_KERNEL)) return -ENOMEM; cpumask_copy(cpus_to_visit, cpu_possible_mask); pdev_kobj = &pdev->dev.kobj; ret = cpufreq_register_notifier(&freq_qos_tracer_notifier, CPUFREQ_POLICY_NOTIFIER); if (ret) free_cpumask_var(cpus_to_visit); pr_info("Initialized Exynos freq QoS tracing driver\n"); return ret; } static const struct of_device_id of_freq_qos_tracer_match[] = { { .compatible = "samsung,freq-qos-tracer", }, { }, }; MODULE_DEVICE_TABLE(of, of_freq_qos_tracer_match); static struct platform_driver freq_qos_tracer_driver = { .driver = { .name = "freq-qos-tracer", .owner = THIS_MODULE, .of_match_table = of_freq_qos_tracer_match, }, .probe = freq_qos_tracer_probe, }; module_platform_driver(freq_qos_tracer_driver); MODULE_DESCRIPTION("CPUFreq QoS Tracer"); MODULE_LICENSE("GPL");