123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Samsung Block Statistics
- *
- * Copyright (C) 2021 Manjong Lee <[email protected]>
- * Copyright (C) 2021 Junho Kim <[email protected]>
- * Copyright (C) 2021 Changheun Lee <[email protected]>
- * Copyright (C) 2021 Seunghwan Hyun <[email protected]>
- * Copyright (C) 2021 Tran Xuan Nam <[email protected]>
- */
- #include <linux/sysfs.h>
- #include <linux/blk_types.h>
- #include <linux/blkdev.h>
- #include <linux/blk-mq.h>
- #include <linux/cpufreq.h>
- #include <linux/log2.h>
- #include <linux/pm_qos.h>
- #include "blk-sec.h"
- struct traffic {
- u64 transferred_bytes;
- int level;
- unsigned int timestamp;
- struct work_struct update_work;
- struct delayed_work notify_work;
- };
- static DEFINE_PER_CPU(u64, transferred_bytes);
- static DEFINE_PER_CPU(struct freq_qos_request, cpufreq_req);
- static struct pm_qos_request cpu_pm_req;
- static unsigned int interval_ms = 1000;
- static unsigned int interval_bytes = 100 * 1024 * 1024;
- static struct traffic traffic;
- #define TL0_UEVENT_DELAY_MS 2000
- #define UPDATE_WORK_TO_TRAFFIC(work) \
- container_of(work, struct traffic, update_work)
- #define NOTIFY_WORK_TO_TRAFFIC(work) \
- container_of(to_delayed_work(work), struct traffic, notify_work)
- static u64 get_transferred_bytes(void)
- {
- u64 bytes = 0;
- int cpu;
- for_each_possible_cpu(cpu)
- bytes += per_cpu(transferred_bytes, cpu);
- return bytes;
- }
- /*
- * Convert throughput to level. Level is defined as below:
- * 0: 0 - "< 100" MB/s
- * 1: 100 - "< 200" MB/s
- * 2: 200 - "< 400" MB/s
- * 3: 400 - "< 800" MB/s
- * ...so on
- */
- static int tp2level(int tput)
- {
- if (tput < 100)
- return 0;
- return (int) ilog2(tput / 100) + 1;
- }
- static void notify_traffic_level(struct traffic *traffic)
- {
- #define BUF_SIZE 16
- char buf[BUF_SIZE];
- char *envp[] = { "NAME=IO_TRAFFIC", buf, NULL, };
- int ret;
- if (unlikely(IS_ERR(blk_sec_dev)))
- return;
- memset(buf, 0, BUF_SIZE);
- snprintf(buf, BUF_SIZE, "LEVEL=%d", traffic->level);
- ret = kobject_uevent_env(&blk_sec_dev->kobj, KOBJ_CHANGE, envp);
- if (ret)
- pr_err("%s: couldn't send uevent (%d)", __func__, ret);
- }
- #define MB(x) ((x) / 1024 / 1024)
- static void update_traffic_level(struct work_struct *work)
- {
- struct traffic *traffic = UPDATE_WORK_TO_TRAFFIC(work);
- struct traffic old = *traffic;
- unsigned int duration_ms;
- u64 amount;
- int tput;
- int delay = 0;
- traffic->transferred_bytes = get_transferred_bytes();
- traffic->timestamp = jiffies_to_msecs(jiffies);
- duration_ms = traffic->timestamp - old.timestamp;
- amount = traffic->transferred_bytes - old.transferred_bytes;
- tput = MB(amount) * 1000 / duration_ms;
- traffic->level = tp2level(tput);
- if (!!traffic->level == !!old.level)
- return;
- if (traffic->level == 0) /* level !0 -> 0 */
- delay = msecs_to_jiffies(TL0_UEVENT_DELAY_MS);
- cancel_delayed_work_sync(&traffic->notify_work);
- schedule_delayed_work(&traffic->notify_work, delay);
- }
- static void send_uevent(struct work_struct *work)
- {
- struct traffic *traffic = NOTIFY_WORK_TO_TRAFFIC(work);
- notify_traffic_level(traffic);
- }
- void blk_sec_stat_traffic_update(struct request *rq, unsigned int data_size)
- {
- unsigned int duration_ms;
- u64 amount;
- if (req_op(rq) > REQ_OP_WRITE)
- return;
- this_cpu_add(transferred_bytes, data_size);
- duration_ms = jiffies_to_msecs(jiffies) - traffic.timestamp;
- amount = get_transferred_bytes() - traffic.transferred_bytes;
- if ((duration_ms < interval_ms) && (amount < interval_bytes))
- return;
- schedule_work(&traffic.update_work);
- }
- static void init_traffic(struct traffic *traffic)
- {
- traffic->transferred_bytes = 0;
- traffic->level = 0;
- traffic->timestamp = jiffies_to_msecs(jiffies);
- INIT_WORK(&traffic->update_work, update_traffic_level);
- INIT_DELAYED_WORK(&traffic->notify_work, send_uevent);
- }
- static void allow_cpu_lpm(bool enable)
- {
- if (enable)
- cpu_latency_qos_update_request(&cpu_pm_req, PM_QOS_DEFAULT_VALUE);
- else
- cpu_latency_qos_update_request(&cpu_pm_req, 0);
- }
- static ssize_t transferred_bytes_show(struct kobject *kobj,
- struct kobj_attribute *attr, char *buf)
- {
- return scnprintf(buf, PAGE_SIZE, "%llu\n", get_transferred_bytes());
- }
- static ssize_t cpufreq_min_show(struct kobject *kobj,
- struct kobj_attribute *attr, char *buf)
- {
- struct freq_qos_request *req;
- int len = 0;
- int i;
- for_each_possible_cpu(i) {
- req = &per_cpu(cpufreq_req, i);
- if (IS_ERR_OR_NULL(req->qos))
- continue;
- len += scnprintf(buf + len, PAGE_SIZE - len, "%d: %d, %d, %d\n",
- i,
- req->qos->min_freq.target_value,
- req->qos->min_freq.default_value,
- req->qos->min_freq.no_constraint_value);
- }
- return len;
- }
- static ssize_t cpufreq_min_store(struct kobject *kobj,
- struct kobj_attribute *attr, const char *buf, size_t count)
- {
- struct freq_qos_request *req;
- struct cpufreq_policy *policy;
- s32 cpufreq_min;
- int i;
- int ret;
- ret = kstrtoint(buf, 10, &cpufreq_min);
- if (ret)
- return ret;
- for_each_possible_cpu(i) {
- req = &per_cpu(cpufreq_req, i);
- if (IS_ERR_OR_NULL(req->qos)) {
- policy = cpufreq_cpu_get(i);
- if (!policy)
- continue;
- freq_qos_add_request(&policy->constraints,
- req, FREQ_QOS_MIN, cpufreq_min);
- cpufreq_cpu_put(policy);
- }
- freq_qos_update_request(req, cpufreq_min);
- }
- return count;
- }
- static ssize_t cpu_lpm_enabled_show(struct kobject *kobj,
- struct kobj_attribute *attr, char *buf)
- {
- if (IS_ERR_OR_NULL(cpu_pm_req.qos))
- return 0;
- return scnprintf(buf, PAGE_SIZE, "%d: %d, %d, %d\n",
- !!cpu_pm_req.qos->target_value,
- cpu_pm_req.qos->target_value,
- cpu_pm_req.qos->default_value,
- cpu_pm_req.qos->no_constraint_value);
- }
- static ssize_t cpu_lpm_enabled_store(struct kobject *kobj,
- struct kobj_attribute *attr, const char *buf, size_t count)
- {
- int enable;
- int ret;
- ret = kstrtoint(buf, 10, &enable);
- if (ret)
- return ret;
- allow_cpu_lpm(!!enable);
- return count;
- }
- static struct kobj_attribute transferred_bytes_attr =
- __ATTR(transferred_bytes, 0444, transferred_bytes_show, NULL);
- static struct kobj_attribute cpufreq_min_attr =
- __ATTR(cpufreq_min, 0600, cpufreq_min_show, cpufreq_min_store);
- static struct kobj_attribute cpu_lpm_enable_attr =
- __ATTR(cpu_lpm_enable, 0600, cpu_lpm_enabled_show, cpu_lpm_enabled_store);
- static const struct attribute *blk_sec_stat_traffic_attrs[] = {
- &transferred_bytes_attr.attr,
- &cpufreq_min_attr.attr,
- &cpu_lpm_enable_attr.attr,
- NULL,
- };
- int blk_sec_stat_traffic_init(struct kobject *kobj)
- {
- if (!kobj)
- return -EINVAL;
- init_traffic(&traffic);
- cpu_latency_qos_add_request(&cpu_pm_req, PM_QOS_DEFAULT_VALUE);
- return sysfs_create_files(kobj, blk_sec_stat_traffic_attrs);
- }
- void blk_sec_stat_traffic_exit(struct kobject *kobj)
- {
- if (!kobj)
- return;
- allow_cpu_lpm(true);
- cpu_latency_qos_remove_request(&cpu_pm_req);
- cancel_delayed_work_sync(&traffic.notify_work);
- sysfs_remove_files(kobj, blk_sec_stat_traffic_attrs);
- }
|