調度器27—Freq Qos 和 限頻流程


基於 Linux-5.10

一、概述

freq qos 主要用於cpu調頻使用的,基於qos中的實現。與pm qos不同的是,前者只有系統級實現,位於 kernel/power/qos.c 中。


二、相關結構

enum pm_qos_req_action {
    PM_QOS_ADD_REQ,        /* Add a new request */
    PM_QOS_UPDATE_REQ,    /* Update an existing request */
    PM_QOS_REMOVE_REQ    /* Remove an existing request */
};

#define FREQ_QOS_MIN_DEFAULT_VALUE    0
#define FREQ_QOS_MAX_DEFAULT_VALUE    S32_MAX

enum pm_qos_type {
    PM_QOS_UNITIALIZED,
    PM_QOS_MAX,        /* return the largest value */
    PM_QOS_MIN,        /* return the smallest value */
};

enum freq_qos_req_type {
    FREQ_QOS_MIN = 1,
    FREQ_QOS_MAX,
};

struct pm_qos_constraints {
    struct plist_head list; //所有此限制的freq_qos_request通過其pnode節點掛在這里
    /* Do not change to 64 bit */
    s32 target_value; //此限制,Qos最終體現的值
    s32 default_value; 
    s32 no_constraint_value;
    enum pm_qos_type type;
    struct blocking_notifier_head *notifiers;
};

struct freq_constraints {
    struct pm_qos_constraints min_freq;
    struct blocking_notifier_head min_freq_notifiers;
    struct pm_qos_constraints max_freq;
    struct blocking_notifier_head max_freq_notifiers;
};

struct freq_qos_request {
    enum freq_qos_req_type type;
    struct plist_node pnode; //數據是存在其prio成員中
    struct freq_constraints *qos;
    ANDROID_OEM_DATA_ARRAY(1, 2);
};

對頻點的限制是一個區間,有最大值和最小值,所以 freq_constraints 中使用兩個 pm_qos_constraints 成員表示。由結構中的兩個notifier head可知,極大值和極小值改變后也是分別通知的。


三、相關函數

1. freq_constraints_init

/**
 * freq_constraints_init - Initialize frequency QoS constraints.
 * @qos: Frequency QoS constraints to initialize.
 */
void freq_constraints_init(struct freq_constraints *qos)
{
    struct pm_qos_constraints *c;

    c = &qos->min_freq;
    plist_head_init(&c->list);
    c->target_value = FREQ_QOS_MIN_DEFAULT_VALUE;
    c->default_value = FREQ_QOS_MIN_DEFAULT_VALUE;
    c->no_constraint_value = FREQ_QOS_MIN_DEFAULT_VALUE;
    c->type = PM_QOS_MAX;
    c->notifiers = &qos->min_freq_notifiers;
    BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers);

    c = &qos->max_freq;
    plist_head_init(&c->list);
    c->target_value = FREQ_QOS_MAX_DEFAULT_VALUE;
    c->default_value = FREQ_QOS_MAX_DEFAULT_VALUE;
    c->no_constraint_value = FREQ_QOS_MAX_DEFAULT_VALUE;
    c->type = PM_QOS_MIN;
    c->notifiers = &qos->max_freq_notifiers;
    BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers);
}

新分配一個 freq_constraints 結構后可以直接調用此函數,函數中分別對min_freq和max_freq進行初始化,這個函數沒有export導出來。

注意其type的初始化。min_freq 這個限制賦值的type竟然是PM_QOS_MAX,而 max_freq 這個限制賦值的type竟然是PM_QOS_MIN!這樣當限制最大頻點的時候,pm_qos判斷是PM_QOS_MIN,那么plist鏈表上生效的就是最小值,也就是說對最大頻點的限制,誰限制的小誰生效。當限制最小頻點的時候,pm_qos判斷是PM_QOS_MAX,那么plist鏈表上生效的就是最大值,也就是說對最小頻點的限制,誰限制的大誰生效。這是反着來利用Qos機制的!

2. freq_qos_add_notifier

/**
 * freq_qos_add_notifier - Add frequency QoS change notifier.
 * @qos: List of requests to add the notifier to.
 * @type: Request type.
 * @notifier: Notifier block to add.
 */
int freq_qos_add_notifier(struct freq_constraints *qos, enum freq_qos_req_type type, struct notifier_block *notifier)
{
    int ret;

    if (IS_ERR_OR_NULL(qos) || !notifier)
        return -EINVAL;

    switch (type) {
    case FREQ_QOS_MIN:
        ret = blocking_notifier_chain_register(qos->min_freq.notifiers, notifier);
        break;
    case FREQ_QOS_MAX:
        ret = blocking_notifier_chain_register(qos->max_freq.notifiers, notifier);
        break;
    default:
        WARN_ON(1);
        ret = -EINVAL;
    }

    return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_add_notifier);

注冊一個notifier,根據參數 type,決定使用max的或min的 pm_qos_constraints::notifiers,notifier 參數中有指定優先級,優先級數值大的插入在鏈表前面,優先級數值小的插入后面,優先級數值若相同,先插入的在前面。notifier->notifier_call()里面會對感感興趣的action進行響應。

其中,Qos最終頻點限制值改變了,也是通過這個notifier機制更新通知修改的,也就是說cpufreq驅動必須注冊兩個notifier根據Qos來設置頻點值,一個是設置MAX限制,一個是設置MIN限制。

3. freq_qos_remove_notifier

/**
 * freq_qos_remove_notifier - Remove frequency QoS change notifier.
 * @qos: List of requests to remove the notifier from.
 * @type: Request type.
 * @notifier: Notifier block to remove.
 */
int freq_qos_remove_notifier(struct freq_constraints *qos, enum freq_qos_req_type type, struct notifier_block *notifier)
{
    int ret;

    if (IS_ERR_OR_NULL(qos) || !notifier)
        return -EINVAL;

    switch (type) {
    case FREQ_QOS_MIN:
        ret = blocking_notifier_chain_unregister(qos->min_freq.notifiers, notifier);
        break;
    case FREQ_QOS_MAX:
        ret = blocking_notifier_chain_unregister(qos->max_freq.notifiers, notifier);
        break;
    default:
        WARN_ON(1);
        ret = -EINVAL;
    }

    return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_remove_notifier);

將此 notifier_block 結構從指定的 constraints 的 notifiers 鏈表上刪除。任何對Qos的此限制感興趣的都需要注冊notifier,不再感興趣時刪除。在不需要對頻點做要求時需要刪除自己的 freq_qos_request 結構,否則它可能持續在生效,導致其它 

freq_qos_request 表示的頻點無法生效。

4. freq_qos_apply

/**
 * freq_qos_apply - Add/modify/remove frequency QoS request.
 * @req: Constraint request to apply.
 * @action: Action to perform (add/update/remove).
 * @value: Value to assign to the QoS request.
 *
 * This is only meant to be called from inside pm_qos, not drivers.
 */
int freq_qos_apply(struct freq_qos_request *req, enum pm_qos_req_action action, s32 value)
{
    int ret;

    switch(req->type) {
    case FREQ_QOS_MIN:
        ret = pm_qos_update_target(&req->qos->min_freq, &req->pnode, action, value);
        break;
    case FREQ_QOS_MAX:
        ret = pm_qos_update_target(&req->qos->max_freq, &req->pnode, action, value);
        break;
    default:
        ret = -EINVAL;
    }

    return ret;
}

只是在qos.c內部使用,沒有導出。

/**
 * pm_qos_update_target - Update a list of PM QoS constraint requests.
 * @c: List of PM QoS requests.
 * @node: Target list entry.
 * @action: Action to carry out (add, update or remove).
 * @value: New request value for the target list entry.
 *
 * Update the given list of PM QoS constraint requests, @c, by carrying an
 * @action involving the @node list entry and @value on it.
 *
 * The recognized values of @action are PM_QOS_ADD_REQ (store @value in @node
 * and add it to the list), PM_QOS_UPDATE_REQ (remove @node from the list, store
 * @value in it and add it to the list again), and PM_QOS_REMOVE_REQ (remove
 * @node from the list, ignore @value).
 *
 * Return: 1 if the aggregate constraint value has changed, 0  otherwise.
 */
int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node, enum pm_qos_req_action action, int value)
{
    int prev_value, curr_value, new_value;
    unsigned long flags;

    spin_lock_irqsave(&pm_qos_lock, flags);

    prev_value = pm_qos_get_value(c); //根據c->type是MAX還是MIN分別返回最后一個元素和第一個元素的prio的值(請求的值越大,越插入在后面)
    if (value == PM_QOS_DEFAULT_VALUE) //-1就是要設置默認值
        new_value = c->default_value;
    else
        new_value = value;

    switch (action) {
    case PM_QOS_REMOVE_REQ:
        plist_del(node, &c->list); //從plist中刪除此 freq_qos_request 結構
        break;
    case PM_QOS_UPDATE_REQ:
        /*
         * To change the list, atomically remove, reinit with new value
         * and add, then see if the aggregate has changed.
         */
        plist_del(node, &c->list); //從plist中刪除此 freq_qos_request 結構,然后再重新插入
        fallthrough;
    case PM_QOS_ADD_REQ:
        plist_node_init(node, new_value); //node->prio = new_value; 更新value
        plist_add(node, &c->list); //重新插入plist鏈表,prio(也就是request的value值)越大越插入后面,越小越插入前面
        break;
    default:
        /* no action */
        ;
    }

    curr_value = pm_qos_get_value(c);
    pm_qos_set_value(c, curr_value); //c->target_value=value,獲取value時返回它

    spin_unlock_irqrestore(&pm_qos_lock, flags);

    trace_pm_qos_update_target(action, prev_value, curr_value);

    if (prev_value == curr_value)
        return 0;

    /*最終結果就是發出一個notifier*/
    if (c->notifiers)
        blocking_notifier_call_chain(c->notifiers, curr_value, NULL);

    return 1;
}

static int pm_qos_get_value(struct pm_qos_constraints *c)
{
    if (plist_head_empty(&c->list))
        return c->no_constraint_value; //empty 就返回 no_constraint_value

    switch (c->type) {
    case PM_QOS_MIN:
        return plist_first(&c->list)->prio; //最小就返回第一個元素

    case PM_QOS_MAX:
        return plist_last(&c->list)->prio; //最大返回最后一個元素

    default:
        WARN(1, "Unknown PM QoS type in %s\n", __func__);
        return PM_QOS_DEFAULT_VALUE;
    }
}

注意,在add request時,prio越大(value值越大),越插入靠后,prio值越小,越插入靠前。這里獲取PM_QOS_MIN值,返回的是plist鏈表第一個元素的值,返回的是最小值。獲取PM_QOS_MAX值,返回的是plist鏈表最后一個元素的值,返回的是最大值。

 

freq_qos_add_request(&qos, &req, FREQ_QOS_MIN, FREQ_QOS_MAX_DEFAULT_VALUE/*S32_MAX*/) 就表示不限制最大頻點了。之后再通過freq_qos_update_request(&req)來更新限制,否則任何其它人設置都無效了,因為這里已經設置了最大值,沒有更大的合法值可以去設置了。

有個trace: trace_pm_qos_update_target,但是沒有太大幫助,沒有注明是哪個cluster的。

kthread-270     [004] .... 220691.970715: pm_qos_update_target: action=UPDATE_REQ prev_value=150 curr_value=2000000000
kthread-270     [004] .... 220691.970846: pm_qos_update_target: action=UPDATE_REQ prev_value=2000000000 curr_value=150

freq_qos_apply() 函數最終只是判斷當前最終體現值 curr_value 和之前最終體現值 prev_value 是否相等,若是相等返回0,不相等就通過 pm_qos_constraints::notifiers 發出一個通知,這里只是一個通知而已。

5. freq_qos_add_request

/**
 * freq_qos_add_request - Insert new frequency QoS request into a given list.
 * @qos: Constraints to update.
 * @req: Preallocated request object.
 * @type: Request type.
 * @value: Request value.
 *
 * Insert a new entry into the @qos list of requests, recompute the effective
 * QoS constraint value for that list and initialize the @req object.  The
 * caller needs to save that object for later use in updates and removal.
 *
 * Return 1 if the effective constraint value has changed, 0 if the effective
 * constraint value has not changed, or a negative error code on failures.
 */
int freq_qos_add_request(struct freq_constraints *qos, struct freq_qos_request *req, enum freq_qos_req_type type, s32 value)
{
    int ret;

    if (IS_ERR_OR_NULL(qos) || !req)
        return -EINVAL;

    if (WARN(freq_qos_request_active(req), "%s() called for active request\n", __func__))
        return -EINVAL;

    req->qos = qos;
    req->type = type;
    ret = freq_qos_apply(req, PM_QOS_ADD_REQ, value);
    if (ret < 0) {
        req->qos = NULL;
        req->type = 0;
    }

    trace_android_vh_freq_qos_add_request(qos, req, type, value, ret);

    return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_add_request);

req 是新分配直接使用的。調用結果就是向指定的 pm_qos_constraints::list 鏈表上插入一個 freq_qos_request 成員,高優先級(數值大,prio=value)的插入在plist后面,低優先級的插入在前面。若是這個 request 值最終使 Qos 的值改變通過notifier發出通知的話,就返回1,否則返回0,失敗返回負的錯誤碼。可以看出添加request是實時生效的。

6. freq_qos_update_request

/**
 * freq_qos_update_request - Modify existing frequency QoS request.
 * @req: Request to modify.
 * @new_value: New request value.
 *
 * Update an existing frequency QoS request along with the effective constraint
 * value for the list of requests it belongs to.
 *
 * Return 1 if the effective constraint value has changed, 0 if the effective
 * constraint value has not changed, or a negative error code on failures.
 */
int freq_qos_update_request(struct freq_qos_request *req, s32 new_value)
{
    if (!req)
        return -EINVAL;

    if (WARN(!freq_qos_request_active(req), "%s() called for unknown object\n", __func__))
        return -EINVAL;

    trace_android_vh_freq_qos_update_request(req, new_value); mtk_freq_qos_update_request //hook只是一個打印
    if (req->pnode.prio == new_value)
        return 0;

    return freq_qos_apply(req, PM_QOS_UPDATE_REQ, new_value);
}
EXPORT_SYMBOL_GPL(freq_qos_update_request);

使用新值替換 pm_qos_constraints::list 上對應 freq_qos_request::pnode::prio 的舊值。若最終Qos值改變了,發出通知並返回1,若最終Qos的值沒有變,返回0。

7. freq_qos_remove_request

/**
 * freq_qos_remove_request - Remove frequency QoS request from its list.
 * @req: Request to remove.
 *
 * Remove the given frequency QoS request from the list of constraints it
 * belongs to and recompute the effective constraint value for that list.
 *
 * Return 1 if the effective constraint value has changed, 0 if the effective
 * constraint value has not changed, or a negative error code on failures.
 */
int freq_qos_remove_request(struct freq_qos_request *req)
{
    int ret;

    if (!req)
        return -EINVAL;

    if (WARN(!freq_qos_request_active(req), "%s() called for unknown object\n", __func__))
        return -EINVAL;

    trace_android_vh_freq_qos_remove_request(req);
    ret = freq_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); //此處參數3基本沒用
    req->qos = NULL;
    req->type = 0;

    return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_remove_request);

移除 pm_qos_constraints::list 上對應 freq_qos_request ,移除后,若最終Qos值改變了,發出通知並返回1,若最終Qos的值沒有變,返回0。

8. freq_qos_read_value

/*
 * freq_qos_read_value - Get frequency QoS constraint for a given list.
 * @qos: Constraints to evaluate.
 * @type: QoS request type.
 */
s32 freq_qos_read_value(struct freq_constraints *qos, enum freq_qos_req_type type)
{
    s32 ret;

    switch (type) {
    case FREQ_QOS_MIN:
        ret = IS_ERR_OR_NULL(qos) ? FREQ_QOS_MIN_DEFAULT_VALUE : pm_qos_read_value(&qos->min_freq);
        break;
    case FREQ_QOS_MAX:
        ret = IS_ERR_OR_NULL(qos) ? FREQ_QOS_MAX_DEFAULT_VALUE : pm_qos_read_value(&qos->max_freq);
        break;
    default:
        WARN_ON(1);
        ret = 0;
    }

    return ret;
}

此函數沒有導出來。返回指定 pm_qos_constraints 的 target_value 值。它是在 pm_qos_update_target() 中更新的。若qos參數傳null,就可以得到默認的最大最小值。

 

四、邏輯介紹

1. 調頻模塊先注冊頻點設置notifier函數

static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) //cpufreq.c
{
    ...
    freq_constraints_init(&policy->constraints);

    //實際設置頻點的函數
    policy->nb_min.notifier_call = cpufreq_notifier_min;
    policy->nb_max.notifier_call = cpufreq_notifier_max;
    
    freq_qos_add_notifier(&policy->constraints, FREQ_QOS_MIN, &policy->nb_min);
    freq_qos_add_notifier(&policy->constraints, FREQ_QOS_MAX, &policy->nb_max);
    ...
}

//合二為一,通過work串行執行
static int cpufreq_notifier_min(struct notifier_block *nb, unsigned long freq, void *data)
{
    struct cpufreq_policy *policy = container_of(nb, struct cpufreq_policy, nb_min);

    schedule_work(&policy->update); //handle_update
    return 0;
}
static int cpufreq_notifier_max(struct notifier_block *nb, unsigned long freq, void *data)
{
    struct cpufreq_policy *policy = container_of(nb, struct cpufreq_policy, nb_max);

    schedule_work(&policy->update);
    return 0;
}

static void handle_update(struct work_struct *work)
{
    struct cpufreq_policy *policy = container_of(work, struct cpufreq_policy, update);

    down_write(&policy->rwsem);
    refresh_frequency_limits(policy);
    up_write(&policy->rwsem);
}

void refresh_frequency_limits(struct cpufreq_policy *policy)
{
    if (!policy_is_inactive(policy)) { //return cpumask_empty(policy->cpus);
        cpufreq_set_policy(policy, policy->governor, policy->policy);
    }
}

static int cpufreq_set_policy(struct cpufreq_policy *policy, struct cpufreq_governor *new_gov, unsigned int new_pol)
{
    ...
    new_data.freq_table = policy->freq_table;
    new_data.cpu = policy->cpu;

    //通過Qos獲取最大最小頻點限制
    new_data.min = freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
    new_data.max = freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);

    cpufreq_driver->verify(&new_data);

    //限制值設置到policy中
    policy->min = new_data.min;
    policy->max = new_data.max;
    trace_cpu_frequency_limits(policy);

    if (new_gov == policy->governor) {
        cpufreq_governor_limits(policy); //這里調用到 policy->governor->limits(policy);
        return 0;
    }
    ...
}

static void sugov_limits(struct cpufreq_policy *policy)
{
    struct sugov_policy *sg_policy = policy->governor_data;

    sg_policy->limits_changed = true; //只是設置了一個標記
}


static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
{
    s64 delta_ns;

    //若設置了限頻,就不等達到頻點變化延遲了,直接設置頻點,新設置的頻點會受到新限制的鉗位
    if (unlikely(sg_policy->limits_changed)) {
        sg_policy->limits_changed = false;
        sg_policy->need_freq_update = true; //唯一設置位置
        return true;
    }

    delta_ns = time - sg_policy->last_freq_update_time;
    return delta_ns >= sg_policy->min_rate_limit_ns;
}

//調頻函數
static void sugov_update_shared(struct update_util_data *hook, u64 time, unsigned int flags)
{
    if (sugov_should_update_freq(sg_policy, time)) { //判斷需要設置才設置
        next_f = sugov_next_freq_shared(sg_cpu, time);

        if (sg_policy->policy->fast_switch_enabled)
            sugov_fast_switch(sg_policy, time, next_f); //設置頻點
        else
            sugov_deferred_update(sg_policy, time, next_f);
    }
}


static void sugov_fast_switch(struct sugov_policy *sg_policy, u64 time, unsigned int next_freq)
{
    struct cpufreq_policy *policy = sg_policy->policy;

    //這里也受 sg_policy->need_freq_update 的值影響
    if (!sugov_update_next_freq(sg_policy, time, next_freq))
        return;

    next_freq = cpufreq_driver_fast_switch(policy, next_freq);
    if (!next_freq)
        return;

    policy->cur = next_freq;

}

unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, unsigned int target_freq)
{
    unsigned int freq;
    int cpu;

    //尊重Qos的限制值
    target_freq = clamp_val(target_freq, policy->min, policy->max);
    //調用驅動設置頻點
    freq = cpufreq_driver->fast_switch(policy, target_freq);
}

可以看到,頻率限制時對頻點的設置其實並不是完全實時的,它只是設置一個標志位而已。然后需要等到有調頻調用,即cpufreq_update_util --> sugov_update_shared/sugov_update_single --> 判斷有pending的限頻導致的設置,就不用等,立即設置。

 

2. 其它模塊使用 freq_qos_add_request() / freq_qos_update_request() 來限制頻點值

void set_each_cluster_maxfreq_to_2G() {
    struct freq_qos_request req;
    struct cpufreq_policy *policy;

    //每個cpu都限制到2GHz
    for_each_possible_cpu(cpu) {
        policy = cpufreq_cpu_get(cpu);
        freq_qos_add_request(&policy->constraints, &req, FREQ_QOS_MAX, 2000000000);
        cpu = cpumask_last(policy->related_cpus);//just cpu0 4 7
        cpufreq_cpu_put(policy);
    }
}

 

五、限制生效流程

由 Freq Qos 實現可值,當 qos_update 使 Qos 的最終限制結果改變時,會發出notifier,因此需要注冊notifier block,並在接收到notifier通知后更新限頻值,若當前頻點不在新的限制范圍內的話,還要設置當前頻點。

//drivers/cpufreq/cpufreq.c
static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu)
{
    struct cpufreq_policy *policy;
    struct device *dev = get_cpu_device(cpu);

    ...
    freq_constraints_init(&policy->constraints);

    //收到notifier通知后的回調函數
    policy->nb_min.notifier_call = cpufreq_notifier_min;
    policy->nb_max.notifier_call = cpufreq_notifier_max;

    //注冊 Freq Qos 限頻后發出通知的響應函數
    ret = freq_qos_add_notifier(&policy->constraints, FREQ_QOS_MIN, &policy->nb_min);
    ret = freq_qos_add_notifier(&policy->constraints, FREQ_QOS_MAX, &policy->nb_max);

    //這個是處理函數,異步的
    INIT_WORK(&policy->update, handle_update);
    ...
}

響應回調函數:

static int cpufreq_notifier_min(struct notifier_block *nb, unsigned long freq, void *data)
{
    struct cpufreq_policy *policy = container_of(nb, struct cpufreq_policy, nb_min);

    schedule_work(&policy->update);
    return 0;
}

static int cpufreq_notifier_max(struct notifier_block *nb, unsigned long freq, void *data)
{
    struct cpufreq_policy *policy = container_of(nb, struct cpufreq_policy, nb_max);

    schedule_work(&policy->update);
    return 0;
}

static inline bool schedule_work(struct work_struct *work)
{
    return queue_work(system_wq, work); //就會調用到 handle_update() 
}

異步執行策略變更設置:

static void handle_update(struct work_struct *work)
{
    struct cpufreq_policy *policy = container_of(work, struct cpufreq_policy, update);

    pr_debug("handle_update for cpu %u called\n", policy->cpu);
    down_write(&policy->rwsem);
    refresh_frequency_limits(policy);
    up_write(&policy->rwsem);
}


void refresh_frequency_limits(struct cpufreq_policy *policy)
{
    if (!policy_is_inactive(policy)) {
        pr_debug("updating policy for CPU %u\n", policy->cpu);

        cpufreq_set_policy(policy, policy->governor, policy->policy);
    }
}
EXPORT_SYMBOL(refresh_frequency_limits);

實際上是調用 cpufreq_set_policy() 來使限頻策略更新的。

static int cpufreq_set_policy(struct cpufreq_policy *policy, struct cpufreq_governor *new_gov, unsigned int new_pol)
{
    struct cpufreq_policy_data new_data;

    memcpy(&new_data.cpuinfo, &policy->cpuinfo, sizeof(policy->cpuinfo));
    new_data.freq_table = policy->freq_table;
    new_data.cpu = policy->cpu;


    //從Freq QoS 中讀取min和max值
    new_data.min = freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
    new_data.max = freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);

    //驗證一下,確保min <= max.
    ret = cpufreq_driver->verify(&new_data);

    //policy的min和max成員保存的是生效的最大頻點和最小頻點的限制值
    policy->min = new_data.min;
    policy->max = new_data.max;

    //這里有個trace在限頻策略生效時打印
    trace_cpu_frequency_limits(policy);

    policy->cached_target_freq = UINT_MAX;

    pr_debug("new min and max freqs are %u - %u kHz\n", policy->min, policy->max);

    //cpufreq_driver若是有setpolicy回調則調用,但是通常driver不會定義這個回調
    if (cpufreq_driver->setpolicy) {
        policy->policy = new_pol;
        pr_debug("setting range\n");
        return cpufreq_driver->setpolicy(policy);
    }

    //相同govrnor的限頻走這里
    if (new_gov == policy->governor) {
        pr_debug("governor limits update\n");
        cpufreq_governor_limits(policy); //相同governor走這里
        return 0;
    }

    ...
}

上面從Freq Qos獲取的最大最小頻點限制已經保存到 policy->min 和 policy->max 中了,限制的任務已經完成,之后的頻點設置都會和policy的min和max比較,將頻點鉗位到min和max之間。之后就是實時的使限制值生效了,這是通過調用governor->limits()回調函數來完成的。

static void cpufreq_governor_limits(struct cpufreq_policy *policy)
{
    //調用governor的limit回調來設置頻點
    if (policy->governor->limits)
        policy->governor->limits(policy);
}


static void sugov_limits(struct cpufreq_policy *policy)
{
    struct sugov_policy *sg_policy = policy->governor_data;

    if (!policy->fast_switch_enabled) {
        mutex_lock(&sg_policy->work_lock);
        cpufreq_policy_apply_limits(policy);
        mutex_unlock(&sg_policy->work_lock);
    }

    sg_policy->limits_changed = true;
}

static inline void cpufreq_policy_apply_limits(struct cpufreq_policy *policy)
{
    //只有在當前頻點不在限制范圍內才會設置
    if (policy->max < policy->cur)
        __cpufreq_driver_target(policy, policy->max, CPUFREQ_RELATION_H);
    else if (policy->min > policy->cur)
        __cpufreq_driver_target(policy, policy->min, CPUFREQ_RELATION_L);
}

這里判斷了 policy->fast_switch_enabled 的值,若是為false就會調用下面函數直接設置頻點。若是為true則不會立即設置,而是延后到下一次頻點變更的時候進行設置,在 sugov_update_shared/sugov_update_single 中判斷 sg_policy->limits_changed 為真時會立即更新頻點,忽視up/down_rate_limit_us文件設置的值。

 

六、實驗

1. 實驗代碼

/* 放到 kernel/sched 下面 */

#define pr_fmt(fmt) "freq_qos_debug: " fmt

#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/printk.h>
#include <asm/topology.h>
#include <linux/cpumask.h>
#include <linux/cpufreq.h>
#include <linux/pm_qos.h>
#include <linux/plist.h>
#include <linux/sched/topology.h>
#include "sched.h"


static struct freq_qos_request qos_req[2][3];

static int freq_qos_debug_show(struct seq_file *m, void *v)
{
    int cpu = 4;
    struct freq_qos_request    *pos;
    struct cpufreq_policy *policy;
    struct plist_head *plh_max, *plh_min;

    for_each_possible_cpu(cpu) {
        policy = cpufreq_cpu_get(cpu);
        if (!policy) {
            pr_info("cpufreq_cpu_get return null!\n");
            return -EFAULT;
        }
        seq_printf(m, "policy->max=%u, policy->min=%u, policy->cur=%u\n", policy->max, policy->min, policy->cur);

        plh_max = &policy->constraints.max_freq.list;
        seq_printf(m, "max freq limit:\n");
        plist_for_each_entry(pos, plh_max, pnode) {
            seq_printf(m, "pos->type=%d, pos->pnode.prio=%d\n", pos->type, pos->pnode.prio);
        }

        plh_min = &policy->constraints.min_freq.list;
        seq_printf(m, "min freq limit:\n");
        plist_for_each_entry(pos, plh_min, pnode) {
            seq_printf(m, "pos->type=%d, pos->pnode.prio=%d\n", pos->type, pos->pnode.prio);
        }
        seq_printf(m, "\n");

        cpu = cpumask_last(policy->related_cpus);
        cpufreq_cpu_put(policy);
    }

    return 0;
}

static int freq_qos_debug_open(struct inode *inode, struct file *file)
{
    return single_open(file, freq_qos_debug_show, NULL);
}

static int freq_qos_debug_update_request(int cluster, int freq_req, int choose)
{
    int ret = freq_qos_update_request(&qos_req[choose][cluster], freq_req);
    if (ret == 1) {
        pr_info("new freq_req=%u take effect.\n", freq_req);
    } else if (ret == 0) {
        pr_info("new freq_req=%u not take effect.\n", freq_req);
    } else if (ret < 0) {
        pr_err("new freq_req=%u update failed.\n", freq_req);
    }

    return ret;
}

static ssize_t freq_qos_debug_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{

    int ret;
    int cluster, min_max, freq_req;
    char buffer[64] = {0};

    if (count >= sizeof(buffer)) {
        count = sizeof(buffer) - 1;
    }
    if (copy_from_user(buffer, buf, count)) {
        pr_info("copy_from_user failed\n");
        return -EFAULT;
    }
    ret = sscanf(buffer, "%d %d %d", &cluster, &min_max, &freq_req);
    if(ret != 3){
        pr_info("sscanf failed, ret=%d\n", ret);
        return -EINVAL;
    }
    if ((cluster < 0 || cluster > 2) || (min_max != 0 && min_max != 1)) {
        pr_info("cmd error: cluster=%d, freq=%d, choose=%d\n", cluster, freq_req, min_max);
    }
    pr_info("set: cluster=%d, freq=%d, choose=%s\n", cluster, freq_req, min_max==1 ? "max":"min");

    freq_qos_debug_update_request(cluster, freq_req, min_max);

    return count;
}

//Linux5.10 change file_operations to proc_ops
static const struct proc_ops freq_qos_debug_fops = {
    .proc_open    = freq_qos_debug_open,
    .proc_read    = seq_read,
    .proc_write   = freq_qos_debug_write,
    .proc_lseek  = seq_lseek,
    .proc_release = single_release,
};

static int freq_qos_debug_add_request(void)
{
    struct cpufreq_policy *policy;
    int ret, cpu;
    int i = 0;

    for_each_possible_cpu(cpu) {
        policy = cpufreq_cpu_get(cpu);
        if (!policy) {
            pr_info("cpufreq_cpu_get return null\n");
            return -EFAULT;
        }

        ret = freq_qos_add_request(&policy->constraints, &qos_req[1][i], FREQ_QOS_MAX, FREQ_QOS_MAX_DEFAULT_VALUE);
        if (ret < 0) {
            pr_err("add qos request max failed. cpu=%d\n", cpu);
            return -EFAULT;
        }
        ret = freq_qos_add_request(&policy->constraints, &qos_req[0][i], FREQ_QOS_MIN, FREQ_QOS_MIN_DEFAULT_VALUE);
        if (ret < 0) {
            pr_err("add qos request min failed. cpu=%d\n", cpu);
            return -EFAULT;
        }

        cpu = cpumask_last(policy->related_cpus);
        cpufreq_cpu_put(policy);

        i++;
    }

    return ret;
}


static int __init freq_qos_debug_init(void)
{
    proc_create("freq_qos_debug", S_IRUGO | S_IWUGO, NULL, &freq_qos_debug_fops);

    freq_qos_debug_add_request();

    pr_info("freq_qos_debug probed\n");

    /*
     * 若不編譯成ko,編譯進內核,打印的是 MODULE not defined!
     * 若編譯成ko,打印的是 MODULE defined!
     */
#ifdef MODULE
    pr_info("MODULE defined!\n");
#else
    pr_info("MODULE not defined!\n");
#endif

    return 0;
}
//若不編譯成模塊,改成 late_initcall 仍然會打印 cpufreq_cpu_get return null
late_initcall(freq_qos_debug_init);

MODULE_DESCRIPTION("Freq Qos Debug");
MODULE_LICENSE("GPL v2"); //必須得有

補充:若編譯成ko, MODULE宏就是定義的,此時各種 XXX_initcall(fn) 都為 module_init(fn),是不考慮插入優先級的,見include/linux/module.h。若編譯進內核,則MODULE宏是沒有定義的,是考慮優先級的,XXX_initcall(fn)分別對應各自的優先級,見include/linux/init.h。也比較容易理解,比如一個模塊編譯成了ko,那么它就是在insmod時單獨加載的,指定優先級也沒有意義。

 

2. 實驗結果

# cat /proc/freq_qos_debug
policy->max=1100000, policy->min=500000, policy->cur=500000
max freq limit:
pos->type=2, pos->pnode.prio=1100000
pos->type=2, pos->pnode.prio=1800000
pos->type=2, pos->pnode.prio=2147483647
min freq limit:
pos->type=1, pos->pnode.prio=0
pos->type=1, pos->pnode.prio=200000
pos->type=1, pos->pnode.prio=500000

policy->max=1800000, policy->min=200000, policy->cur=1400000
max freq limit:
pos->type=2, pos->pnode.prio=1800000
pos->type=2, pos->pnode.prio=2850000
pos->type=2, pos->pnode.prio=2147483647
min freq limit:
pos->type=1, pos->pnode.prio=0
pos->type=1, pos->pnode.prio=200000

policy->max=2300000, policy->min=1300000, policy->cur=1300000
max freq limit:
pos->type=2, pos->pnode.prio=2300000
pos->type=2, pos->pnode.prio=3050000
pos->type=2, pos->pnode.prio=2147483647
min freq limit:
pos->type=1, pos->pnode.prio=0
pos->type=1, pos->pnode.prio=1300000

 

3. 實驗總結

通過設置實驗可以看出,max選最小的,min選最大的,實驗和理論對的上。/sys/devices/system/cpu/cpuX/cpufreq 下的 scaling_min_freq 文件,寫它是 freq_qos_update_request() 一個對最小頻點的限制值,cat它顯示的是policy->min.

通過echo設置可以發現,若是設置並生效的min的限制值比當前max得限制值還大,min取max的值,相當於定頻了,可以叫它向下定頻。若設置並生效的max限制值比當前的min得限制值還小,此時max和min的頻點都取設置並生效的max值,也相當於將頻點定到設置的max值上,可以叫它向下定頻。也可以理解為沖突后以對max的設定為准

 

注:prio值越大越插入后面是正確的,通過移植plist鏈表驗證過了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM