| /* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/list_sort.h> |
| #include <linux/msm-bus-board.h> |
| #include <linux/msm_bus_rules.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| #include <linux/msm-bus.h> |
| #include <trace/events/trace_msm_bus.h> |
| |
| struct node_vote_info { |
| int id; |
| u64 ib; |
| u64 ab; |
| u64 clk; |
| }; |
| |
| struct rules_def { |
| int rule_id; |
| int num_src; |
| int state; |
| struct node_vote_info *src_info; |
| struct bus_rule_type rule_ops; |
| bool state_change; |
| struct list_head link; |
| }; |
| |
| struct rule_node_info { |
| int id; |
| void *data; |
| struct raw_notifier_head rule_notify_list; |
| struct rules_def *cur_rule; |
| int num_rules; |
| struct list_head node_rules; |
| struct list_head link; |
| struct rule_apply_rcm_info apply; |
| }; |
| |
| DEFINE_MUTEX(msm_bus_rules_lock); |
| static LIST_HEAD(node_list); |
| static struct rule_node_info *get_node(u32 id, void *data); |
| static int node_rules_compare(void *priv, struct list_head *a, |
| struct list_head *b); |
| |
| #define LE(op1, op2) (op1 <= op2) |
| #define LT(op1, op2) (op1 < op2) |
| #define GE(op1, op2) (op1 >= op2) |
| #define GT(op1, op2) (op1 > op2) |
| #define NB_ID (0x201) |
| |
| static struct rule_node_info *get_node(u32 id, void *data) |
| { |
| struct rule_node_info *node_it = NULL; |
| struct rule_node_info *node_match = NULL; |
| |
| list_for_each_entry(node_it, &node_list, link) { |
| if (node_it->id == id) { |
| if (id == NB_ID) { |
| if (node_it->data == data) { |
| node_match = node_it; |
| break; |
| } |
| } else { |
| node_match = node_it; |
| break; |
| } |
| } |
| } |
| return node_match; |
| } |
| |
| static struct rule_node_info *gen_node(u32 id, void *data) |
| { |
| struct rule_node_info *node_it = NULL; |
| struct rule_node_info *node_match = NULL; |
| |
| list_for_each_entry(node_it, &node_list, link) { |
| if (node_it->id == id) { |
| node_match = node_it; |
| break; |
| } |
| } |
| |
| if (!node_match) { |
| node_match = kzalloc(sizeof(struct rule_node_info), GFP_KERNEL); |
| goto exit_node_match; |
| |
| node_match->id = id; |
| node_match->cur_rule = NULL; |
| node_match->num_rules = 0; |
| node_match->data = data; |
| list_add_tail(&node_match->link, &node_list); |
| INIT_LIST_HEAD(&node_match->node_rules); |
| RAW_INIT_NOTIFIER_HEAD(&node_match->rule_notify_list); |
| pr_debug("Added new node %d to list\n", id); |
| } |
| exit_node_match: |
| return node_match; |
| } |
| |
| static bool do_compare_op(u64 op1, u64 op2, int op) |
| { |
| bool ret = false; |
| |
| switch (op) { |
| case OP_LE: |
| ret = LE(op1, op2); |
| break; |
| case OP_LT: |
| ret = LT(op1, op2); |
| break; |
| case OP_GT: |
| ret = GT(op1, op2); |
| break; |
| case OP_GE: |
| ret = GE(op1, op2); |
| break; |
| case OP_NOOP: |
| ret = true; |
| break; |
| default: |
| pr_info("Invalid OP %d", op); |
| break; |
| } |
| return ret; |
| } |
| |
| static void update_src_id_vote(struct rule_update_path_info *inp_node, |
| struct rule_node_info *rule_node) |
| { |
| struct rules_def *rule; |
| int i; |
| |
| list_for_each_entry(rule, &rule_node->node_rules, link) { |
| for (i = 0; i < rule->num_src; i++) { |
| if (rule->src_info[i].id == inp_node->id) { |
| rule->src_info[i].ib = inp_node->ib; |
| rule->src_info[i].ab = inp_node->ab; |
| rule->src_info[i].clk = inp_node->clk; |
| } |
| } |
| } |
| } |
| |
| static u64 get_field(struct rules_def *rule, int src_id) |
| { |
| u64 field = 0; |
| int i; |
| |
| for (i = 0; i < rule->num_src; i++) { |
| switch (rule->rule_ops.src_field) { |
| case FLD_IB: |
| field += rule->src_info[i].ib; |
| break; |
| case FLD_AB: |
| field += rule->src_info[i].ab; |
| break; |
| case FLD_CLK: |
| field += rule->src_info[i].clk; |
| break; |
| } |
| } |
| |
| return field; |
| } |
| |
| static bool check_rule(struct rules_def *rule, |
| struct rule_update_path_info *inp) |
| { |
| bool ret = false; |
| |
| if (!rule) |
| return ret; |
| |
| switch (rule->rule_ops.op) { |
| case OP_LE: |
| case OP_LT: |
| case OP_GT: |
| case OP_GE: |
| { |
| u64 src_field = get_field(rule, inp->id); |
| |
| ret = do_compare_op(src_field, rule->rule_ops.thresh, |
| rule->rule_ops.op); |
| break; |
| } |
| default: |
| pr_err("Unsupported op %d", rule->rule_ops.op); |
| break; |
| } |
| return ret; |
| } |
| |
| static void match_rule(struct rule_update_path_info *inp_node, |
| struct rule_node_info *node) |
| { |
| struct rules_def *rule; |
| int i; |
| |
| list_for_each_entry(rule, &node->node_rules, link) { |
| for (i = 0; i < rule->num_src; i++) { |
| if (rule->src_info[i].id != inp_node->id) |
| continue; |
| |
| if (check_rule(rule, inp_node)) { |
| trace_bus_rules_matches( |
| (node->cur_rule ? |
| node->cur_rule->rule_id : -1), |
| inp_node->id, inp_node->ab, |
| inp_node->ib, inp_node->clk); |
| if (rule->state == |
| RULE_STATE_NOT_APPLIED) |
| rule->state_change = true; |
| rule->state = RULE_STATE_APPLIED; |
| } else { |
| if (rule->state == |
| RULE_STATE_APPLIED) |
| rule->state_change = true; |
| rule->state = RULE_STATE_NOT_APPLIED; |
| } |
| } |
| } |
| } |
| |
| static void apply_rule(struct rule_node_info *node, |
| struct list_head *output_list) |
| { |
| struct rules_def *rule; |
| struct rules_def *last_rule; |
| |
| last_rule = node->cur_rule; |
| node->cur_rule = NULL; |
| list_for_each_entry(rule, &node->node_rules, link) { |
| if ((rule->state == RULE_STATE_APPLIED) && |
| !node->cur_rule) |
| node->cur_rule = rule; |
| |
| if (node->id == NB_ID) { |
| if (rule->state_change) { |
| rule->state_change = false; |
| raw_notifier_call_chain(&node->rule_notify_list, |
| rule->state, (void *)&rule->rule_ops); |
| } |
| } else { |
| if ((rule->state == RULE_STATE_APPLIED) && |
| (node->cur_rule && |
| (node->cur_rule->rule_id == rule->rule_id))) { |
| node->apply.id = rule->rule_ops.dst_node[0]; |
| node->apply.throttle = rule->rule_ops.mode; |
| node->apply.lim_bw = rule->rule_ops.dst_bw; |
| node->apply.after_clk_commit = false; |
| if (last_rule != node->cur_rule) |
| list_add_tail(&node->apply.link, |
| output_list); |
| if (last_rule) { |
| if (node_rules_compare(NULL, |
| &last_rule->link, |
| &node->cur_rule->link) == -1) |
| node->apply.after_clk_commit = |
| true; |
| } |
| } |
| rule->state_change = false; |
| } |
| } |
| |
| } |
| |
| int msm_rules_update_path(struct list_head *input_list, |
| struct list_head *output_list) |
| { |
| int ret = 0; |
| struct rule_update_path_info *inp_node; |
| struct rule_node_info *node_it = NULL; |
| |
| mutex_lock(&msm_bus_rules_lock); |
| list_for_each_entry(inp_node, input_list, link) { |
| list_for_each_entry(node_it, &node_list, link) { |
| update_src_id_vote(inp_node, node_it); |
| match_rule(inp_node, node_it); |
| } |
| } |
| |
| list_for_each_entry(node_it, &node_list, link) |
| apply_rule(node_it, output_list); |
| mutex_unlock(&msm_bus_rules_lock); |
| return ret; |
| } |
| |
| static bool ops_equal(int op1, int op2) |
| { |
| bool ret = false; |
| |
| switch (op1) { |
| case OP_GT: |
| case OP_GE: |
| case OP_LT: |
| case OP_LE: |
| if (abs(op1 - op2) <= 1) |
| ret = true; |
| break; |
| default: |
| ret = (op1 == op2); |
| } |
| |
| return ret; |
| } |
| |
| static bool is_throttle_rule(int mode) |
| { |
| bool ret = true; |
| |
| if (mode == THROTTLE_OFF) |
| ret = false; |
| |
| return ret; |
| } |
| |
| static int node_rules_compare(void *priv, struct list_head *a, |
| struct list_head *b) |
| { |
| struct rules_def *ra = container_of(a, struct rules_def, link); |
| struct rules_def *rb = container_of(b, struct rules_def, link); |
| int ret = -1; |
| int64_t th_diff = 0; |
| |
| |
| if (ra->rule_ops.mode == rb->rule_ops.mode) { |
| if (ops_equal(ra->rule_ops.op, rb->rule_ops.op)) { |
| if ((ra->rule_ops.op == OP_LT) || |
| (ra->rule_ops.op == OP_LE)) { |
| th_diff = ra->rule_ops.thresh - |
| rb->rule_ops.thresh; |
| if (th_diff > 0) |
| ret = 1; |
| else |
| ret = -1; |
| } else if ((ra->rule_ops.op == OP_GT) || |
| (ra->rule_ops.op == OP_GE)) { |
| th_diff = rb->rule_ops.thresh - |
| ra->rule_ops.thresh; |
| if (th_diff > 0) |
| ret = 1; |
| else |
| ret = -1; |
| } |
| } else { |
| ret = ra->rule_ops.op - rb->rule_ops.op; |
| } |
| } else if (is_throttle_rule(ra->rule_ops.mode) && |
| is_throttle_rule(rb->rule_ops.mode)) { |
| if (ra->rule_ops.mode == THROTTLE_ON) |
| ret = -1; |
| else |
| ret = 1; |
| } else if ((ra->rule_ops.mode == THROTTLE_OFF) && |
| is_throttle_rule(rb->rule_ops.mode)) { |
| ret = 1; |
| } else if (is_throttle_rule(ra->rule_ops.mode) && |
| (rb->rule_ops.mode == THROTTLE_OFF)) { |
| ret = -1; |
| } |
| |
| return ret; |
| } |
| |
| static void print_rules(struct rule_node_info *node_it) |
| { |
| struct rules_def *node_rule = NULL; |
| int i; |
| |
| if (!node_it) { |
| pr_err("%s: no node for found", __func__); |
| return; |
| } |
| |
| pr_info("\n Now printing rules for Node %d cur rule %d\n", |
| node_it->id, |
| (node_it->cur_rule ? node_it->cur_rule->rule_id : -1)); |
| list_for_each_entry(node_rule, &node_it->node_rules, link) { |
| pr_info("\n num Rules %d rule Id %d\n", |
| node_it->num_rules, node_rule->rule_id); |
| pr_info("Rule: src_field %d\n", node_rule->rule_ops.src_field); |
| for (i = 0; i < node_rule->rule_ops.num_src; i++) |
| pr_info("Rule: src %d\n", |
| node_rule->rule_ops.src_id[i]); |
| for (i = 0; i < node_rule->rule_ops.num_dst; i++) |
| pr_info("Rule: dst %d dst_bw %llu\n", |
| node_rule->rule_ops.dst_node[i], |
| node_rule->rule_ops.dst_bw); |
| pr_info("Rule: thresh %llu op %d mode %d State %d\n", |
| node_rule->rule_ops.thresh, |
| node_rule->rule_ops.op, |
| node_rule->rule_ops.mode, |
| node_rule->state); |
| } |
| } |
| |
| void print_all_rules(void) |
| { |
| struct rule_node_info *node_it = NULL; |
| |
| mutex_lock(&msm_bus_rules_lock); |
| list_for_each_entry(node_it, &node_list, link) |
| print_rules(node_it); |
| mutex_unlock(&msm_bus_rules_lock); |
| } |
| |
| void print_rules_buf(char *buf, int max_buf) |
| { |
| struct rule_node_info *node_it = NULL; |
| struct rules_def *node_rule = NULL; |
| int i; |
| int cnt = 0; |
| |
| mutex_lock(&msm_bus_rules_lock); |
| list_for_each_entry(node_it, &node_list, link) { |
| cnt += scnprintf(buf + cnt, max_buf - cnt, |
| "\n Now printing rules for Node %d cur_rule %d\n", |
| node_it->id, |
| (node_it->cur_rule ? node_it->cur_rule->rule_id : -1)); |
| list_for_each_entry(node_rule, &node_it->node_rules, link) { |
| cnt += scnprintf(buf + cnt, max_buf - cnt, |
| "\nNum Rules:%d ruleId %d STATE:%d change:%d\n", |
| node_it->num_rules, node_rule->rule_id, |
| node_rule->state, node_rule->state_change); |
| cnt += scnprintf(buf + cnt, max_buf - cnt, |
| "Src_field %d\n", |
| node_rule->rule_ops.src_field); |
| for (i = 0; i < node_rule->rule_ops.num_src; i++) |
| cnt += scnprintf(buf + cnt, max_buf - cnt, |
| "Src %d Cur Ib %llu Ab %llu\n", |
| node_rule->rule_ops.src_id[i], |
| node_rule->src_info[i].ib, |
| node_rule->src_info[i].ab); |
| for (i = 0; i < node_rule->rule_ops.num_dst; i++) |
| cnt += scnprintf(buf + cnt, max_buf - cnt, |
| "Dst %d dst_bw %llu\n", |
| node_rule->rule_ops.dst_node[0], |
| node_rule->rule_ops.dst_bw); |
| cnt += scnprintf(buf + cnt, max_buf - cnt, |
| "Thresh %llu op %d mode %d\n", |
| node_rule->rule_ops.thresh, |
| node_rule->rule_ops.op, |
| node_rule->rule_ops.mode); |
| } |
| } |
| mutex_unlock(&msm_bus_rules_lock); |
| } |
| |
| static int copy_rule(struct bus_rule_type *src, struct rules_def *node_rule, |
| struct notifier_block *nb) |
| { |
| int i; |
| int ret = 0; |
| |
| memcpy(&node_rule->rule_ops, src, |
| sizeof(struct bus_rule_type)); |
| node_rule->rule_ops.src_id = kzalloc( |
| (sizeof(int) * node_rule->rule_ops.num_src), |
| GFP_KERNEL); |
| if (!node_rule->rule_ops.src_id) { |
| pr_err("%s:Failed to allocate for src_id", |
| __func__); |
| return -ENOMEM; |
| } |
| memcpy(node_rule->rule_ops.src_id, src->src_id, |
| sizeof(int) * src->num_src); |
| |
| |
| if (!nb) { |
| node_rule->rule_ops.dst_node = kzalloc( |
| (sizeof(int) * node_rule->rule_ops.num_dst), |
| GFP_KERNEL); |
| if (!node_rule->rule_ops.dst_node) |
| return -ENOMEM; |
| memcpy(node_rule->rule_ops.dst_node, src->dst_node, |
| sizeof(int) * src->num_dst); |
| } |
| |
| node_rule->num_src = src->num_src; |
| node_rule->src_info = kzalloc( |
| (sizeof(struct node_vote_info) * node_rule->rule_ops.num_src), |
| GFP_KERNEL); |
| if (!node_rule->src_info) { |
| pr_err("%s:Failed to allocate for src_id", |
| __func__); |
| return -ENOMEM; |
| } |
| for (i = 0; i < src->num_src; i++) |
| node_rule->src_info[i].id = src->src_id[i]; |
| |
| return ret; |
| } |
| |
| static bool __rule_register(int num_rules, struct bus_rule_type *rule, |
| struct notifier_block *nb) |
| { |
| struct rule_node_info *node = NULL; |
| int i, j; |
| struct rules_def *node_rule = NULL; |
| int num_dst = 0; |
| bool reg_success = true; |
| |
| if (num_rules <= 0) |
| return false; |
| |
| for (i = 0; i < num_rules; i++) { |
| if (nb) |
| num_dst = 1; |
| else |
| num_dst = rule[i].num_dst; |
| |
| for (j = 0; j < num_dst; j++) { |
| int id = 0; |
| |
| if (nb) |
| id = NB_ID; |
| else |
| id = rule[i].dst_node[j]; |
| |
| node = gen_node(id, nb); |
| if (!node) { |
| pr_info("Error getting rule"); |
| reg_success = false; |
| goto exit_rule_register; |
| } |
| node_rule = kzalloc(sizeof(struct rules_def), |
| GFP_KERNEL); |
| if (!node_rule) { |
| reg_success = false; |
| goto exit_rule_register; |
| } |
| |
| if (copy_rule(&rule[i], node_rule, nb)) { |
| pr_err("Error copying rule"); |
| reg_success = false; |
| goto exit_rule_register; |
| } |
| |
| node_rule->rule_id = node->num_rules++; |
| if (nb) |
| node->data = nb; |
| |
| list_add_tail(&node_rule->link, &node->node_rules); |
| } |
| } |
| list_sort(NULL, &node->node_rules, node_rules_compare); |
| if (nb && nb != node->rule_notify_list.head) |
| raw_notifier_chain_register(&node->rule_notify_list, nb); |
| exit_rule_register: |
| return reg_success; |
| } |
| |
| static int comp_rules(struct bus_rule_type *rulea, struct bus_rule_type *ruleb) |
| { |
| int ret = 1; |
| |
| if (rulea->num_src == ruleb->num_src) |
| ret = memcmp(rulea->src_id, ruleb->src_id, |
| (sizeof(int) * rulea->num_src)); |
| if (!ret && (rulea->num_dst == ruleb->num_dst)) |
| ret = memcmp(rulea->dst_node, ruleb->dst_node, |
| (sizeof(int) * rulea->num_dst)); |
| if (ret || (rulea->dst_bw != ruleb->dst_bw) || |
| (rulea->op != ruleb->op) || (rulea->thresh != ruleb->thresh)) |
| ret = 1; |
| return ret; |
| } |
| |
| void msm_rule_register(int num_rules, struct bus_rule_type *rule, |
| struct notifier_block *nb) |
| { |
| if (!rule || num_rules <= 0) |
| return; |
| |
| mutex_lock(&msm_bus_rules_lock); |
| __rule_register(num_rules, rule, nb); |
| mutex_unlock(&msm_bus_rules_lock); |
| } |
| |
| static bool __rule_unregister(int num_rules, struct bus_rule_type *rule, |
| struct notifier_block *nb) |
| { |
| int i = 0; |
| struct rule_node_info *node = NULL; |
| struct rule_node_info *node_tmp = NULL; |
| struct rules_def *node_rule; |
| struct rules_def *node_rule_tmp; |
| bool match_found = false; |
| |
| if (num_rules <= 0) |
| return false; |
| |
| if (nb) { |
| node = get_node(NB_ID, nb); |
| if (!node) { |
| pr_err("%s: Can't find node", __func__); |
| goto exit_unregister_rule; |
| } |
| match_found = true; |
| list_for_each_entry_safe(node_rule, node_rule_tmp, |
| &node->node_rules, link) { |
| if (comp_rules(&node_rule->rule_ops, |
| &rule[i]) == 0) { |
| list_del(&node_rule->link); |
| kfree(node_rule); |
| match_found = true; |
| node->num_rules--; |
| list_sort(NULL, |
| &node->node_rules, |
| node_rules_compare); |
| break; |
| } |
| } |
| if (!node->num_rules) |
| raw_notifier_chain_unregister( |
| &node->rule_notify_list, nb); |
| } else { |
| for (i = 0; i < num_rules; i++) { |
| match_found = false; |
| |
| list_for_each_entry(node, &node_list, link) { |
| list_for_each_entry_safe(node_rule, |
| node_rule_tmp, &node->node_rules, link) { |
| if (comp_rules(&node_rule->rule_ops, |
| &rule[i]) != 0) |
| continue; |
| list_del(&node_rule->link); |
| kfree(node_rule); |
| match_found = true; |
| node->num_rules--; |
| list_sort(NULL, |
| &node->node_rules, |
| node_rules_compare); |
| break; |
| } |
| } |
| } |
| } |
| |
| list_for_each_entry_safe(node, node_tmp, |
| &node_list, link) { |
| if (!node->num_rules) { |
| pr_debug("Deleting Rule node %d", node->id); |
| list_del(&node->link); |
| kfree(node); |
| } |
| } |
| exit_unregister_rule: |
| return match_found; |
| } |
| |
| void msm_rule_unregister(int num_rules, struct bus_rule_type *rule, |
| struct notifier_block *nb) |
| { |
| if (!rule || num_rules <= 0) |
| return; |
| |
| mutex_lock(&msm_bus_rules_lock); |
| __rule_unregister(num_rules, rule, nb); |
| mutex_unlock(&msm_bus_rules_lock); |
| } |
| |
| bool msm_rule_update(struct bus_rule_type *old_rule, |
| struct bus_rule_type *new_rule, |
| struct notifier_block *nb) |
| { |
| bool rc = true; |
| |
| if (!old_rule || !new_rule) { |
| pr_err("%s:msm_rule_update: void rules, error\n", __func__); |
| return false; |
| } |
| mutex_lock(&msm_bus_rules_lock); |
| if (!__rule_unregister(1, old_rule, nb)) { |
| pr_err("%s:msm_rule_update: failed to unregister old rule\n", |
| __func__); |
| rc = false; |
| goto exit_rule_update; |
| } |
| |
| if (!__rule_register(1, new_rule, nb)) { |
| /* |
| * Registering new rule has failed for some reason, attempt |
| * to re-register the old rule and return error. |
| */ |
| pr_err("%s:msm_rule_update: failed to register new rule\n", |
| __func__); |
| __rule_register(1, old_rule, nb); |
| rc = false; |
| } |
| exit_rule_update: |
| mutex_unlock(&msm_bus_rules_lock); |
| return rc; |
| } |
| |
| void msm_rule_evaluate_rules(int node) |
| { |
| struct msm_bus_client_handle *handle; |
| |
| handle = msm_bus_scale_register(node, node, "tmp-rm", false); |
| if (!handle) |
| return; |
| msm_bus_scale_update_bw(handle, 0, 0); |
| msm_bus_scale_unregister(handle); |
| } |
| |
| bool msm_rule_are_rules_registered(void) |
| { |
| bool ret = false; |
| |
| mutex_lock(&msm_bus_rules_lock); |
| if (list_empty(&node_list)) |
| ret = false; |
| else |
| ret = true; |
| mutex_unlock(&msm_bus_rules_lock); |
| return ret; |
| } |
| |