| /* Copyright (c) 2015-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/debugfs.h> |
| #include <linux/spinlock.h> |
| #include <linux/errno.h> |
| #include <linux/bitops.h> |
| #include <linux/printk.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| |
| #include <linux/pmic-voter.h> |
| |
| #define NUM_MAX_CLIENTS 16 |
| #define DEBUG_FORCE_CLIENT "DEBUG_FORCE_CLIENT" |
| |
| static DEFINE_SPINLOCK(votable_list_slock); |
| static LIST_HEAD(votable_list); |
| |
| static struct dentry *debug_root; |
| |
| struct client_vote { |
| bool enabled; |
| int value; |
| }; |
| |
| struct votable { |
| const char *name; |
| struct list_head list; |
| struct client_vote votes[NUM_MAX_CLIENTS]; |
| int num_clients; |
| int type; |
| int effective_client_id; |
| int effective_result; |
| struct mutex vote_lock; |
| void *data; |
| int (*callback)(struct votable *votable, |
| void *data, |
| int effective_result, |
| const char *effective_client); |
| char *client_strs[NUM_MAX_CLIENTS]; |
| bool voted_on; |
| struct dentry *root; |
| struct dentry *status_ent; |
| u32 force_val; |
| struct dentry *force_val_ent; |
| bool force_active; |
| struct dentry *force_active_ent; |
| }; |
| |
| /** |
| * vote_set_any() |
| * @votable: votable object |
| * @client_id: client number of the latest voter |
| * @eff_res: sets 0 or 1 based on the voting |
| * @eff_id: Always returns the client_id argument |
| * |
| * Note that for SET_ANY voter, the value is always same as enabled. There is |
| * no idea of a voter abstaining from the election. Hence there is never a |
| * situation when the effective_id will be invalid, during election. |
| * |
| * Context: |
| * Must be called with the votable->lock held |
| */ |
| static void vote_set_any(struct votable *votable, int client_id, |
| int *eff_res, int *eff_id) |
| { |
| int i; |
| |
| *eff_res = 0; |
| |
| for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) |
| *eff_res |= votable->votes[i].enabled; |
| |
| *eff_id = client_id; |
| } |
| |
| /** |
| * vote_min() - |
| * @votable: votable object |
| * @client_id: client number of the latest voter |
| * @eff_res: sets this to the min. of all the values amongst enabled voters. |
| * If there is no enabled client, this is set to INT_MAX |
| * @eff_id: sets this to the client id that has the min value amongst all |
| * the enabled clients. If there is no enabled client, sets this |
| * to -EINVAL |
| * |
| * Context: |
| * Must be called with the votable->lock held |
| */ |
| static void vote_min(struct votable *votable, int client_id, |
| int *eff_res, int *eff_id) |
| { |
| int i; |
| |
| *eff_res = INT_MAX; |
| *eff_id = -EINVAL; |
| for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) { |
| if (votable->votes[i].enabled |
| && *eff_res > votable->votes[i].value) { |
| *eff_res = votable->votes[i].value; |
| *eff_id = i; |
| } |
| } |
| if (*eff_id == -EINVAL) |
| *eff_res = -EINVAL; |
| } |
| |
| /** |
| * vote_max() - |
| * @votable: votable object |
| * @client_id: client number of the latest voter |
| * @eff_res: sets this to the max. of all the values amongst enabled voters. |
| * If there is no enabled client, this is set to -EINVAL |
| * @eff_id: sets this to the client id that has the max value amongst all |
| * the enabled clients. If there is no enabled client, sets this to |
| * -EINVAL |
| * |
| * Context: |
| * Must be called with the votable->lock held |
| */ |
| static void vote_max(struct votable *votable, int client_id, |
| int *eff_res, int *eff_id) |
| { |
| int i; |
| |
| *eff_res = INT_MIN; |
| *eff_id = -EINVAL; |
| for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) { |
| if (votable->votes[i].enabled && |
| *eff_res < votable->votes[i].value) { |
| *eff_res = votable->votes[i].value; |
| *eff_id = i; |
| } |
| } |
| if (*eff_id == -EINVAL) |
| *eff_res = -EINVAL; |
| } |
| |
| static int get_client_id(struct votable *votable, const char *client_str) |
| { |
| int i; |
| |
| for (i = 0; i < votable->num_clients; i++) { |
| if (votable->client_strs[i] |
| && (strcmp(votable->client_strs[i], client_str) == 0)) |
| return i; |
| } |
| |
| /* new client */ |
| for (i = 0; i < votable->num_clients; i++) { |
| if (!votable->client_strs[i]) { |
| votable->client_strs[i] |
| = kstrdup(client_str, GFP_KERNEL); |
| if (!votable->client_strs[i]) |
| return -ENOMEM; |
| return i; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| static char *get_client_str(struct votable *votable, int client_id) |
| { |
| if (client_id == -EINVAL) |
| return NULL; |
| |
| return votable->client_strs[client_id]; |
| } |
| |
| void lock_votable(struct votable *votable) |
| { |
| mutex_lock(&votable->vote_lock); |
| } |
| |
| void unlock_votable(struct votable *votable) |
| { |
| mutex_unlock(&votable->vote_lock); |
| } |
| |
| /** |
| * is_client_vote_enabled() - |
| * is_client_vote_enabled_locked() - |
| * The unlocked and locked variants of getting whether a client's |
| vote is enabled. |
| * @votable: the votable object |
| * @client_str: client of interest |
| * |
| * Returns: |
| * True if the client's vote is enabled; false otherwise. |
| */ |
| bool is_client_vote_enabled_locked(struct votable *votable, |
| const char *client_str) |
| { |
| int client_id = get_client_id(votable, client_str); |
| |
| if (client_id < 0) |
| return false; |
| |
| return votable->votes[client_id].enabled; |
| } |
| |
| bool is_client_vote_enabled(struct votable *votable, const char *client_str) |
| { |
| bool enabled; |
| |
| lock_votable(votable); |
| enabled = is_client_vote_enabled_locked(votable, client_str); |
| unlock_votable(votable); |
| return enabled; |
| } |
| |
| /** |
| * get_client_vote() - |
| * get_client_vote_locked() - |
| * The unlocked and locked variants of getting a client's voted |
| * value. |
| * @votable: the votable object |
| * @client_str: client of interest |
| * |
| * Returns: |
| * The value the client voted for. -EINVAL is returned if the client |
| * is not enabled or the client is not found. |
| */ |
| int get_client_vote_locked(struct votable *votable, const char *client_str) |
| { |
| int client_id = get_client_id(votable, client_str); |
| |
| if (client_id < 0) |
| return -EINVAL; |
| |
| if ((votable->type != VOTE_SET_ANY) |
| && !votable->votes[client_id].enabled) |
| return -EINVAL; |
| |
| return votable->votes[client_id].value; |
| } |
| |
| int get_client_vote(struct votable *votable, const char *client_str) |
| { |
| int value; |
| |
| lock_votable(votable); |
| value = get_client_vote_locked(votable, client_str); |
| unlock_votable(votable); |
| return value; |
| } |
| |
| /** |
| * get_effective_result() - |
| * get_effective_result_locked() - |
| * The unlocked and locked variants of getting the effective value |
| * amongst all the enabled voters. |
| * |
| * @votable: the votable object |
| * |
| * Returns: |
| * The effective result. |
| * For MIN and MAX votable, returns -EINVAL when the votable |
| * object has been created but no clients have casted their votes or |
| * the last enabled client disables its vote. |
| * For SET_ANY votable it returns 0 when no clients have casted their votes |
| * because for SET_ANY there is no concept of abstaining from election. The |
| * votes for all the clients of SET_ANY votable is defaulted to false. |
| */ |
| int get_effective_result_locked(struct votable *votable) |
| { |
| if (votable->force_active) |
| return votable->force_val; |
| |
| return votable->effective_result; |
| } |
| |
| int get_effective_result(struct votable *votable) |
| { |
| int value; |
| |
| lock_votable(votable); |
| value = get_effective_result_locked(votable); |
| unlock_votable(votable); |
| return value; |
| } |
| |
| /** |
| * get_effective_client() - |
| * get_effective_client_locked() - |
| * The unlocked and locked variants of getting the effective client |
| * amongst all the enabled voters. |
| * |
| * @votable: the votable object |
| * |
| * Returns: |
| * The effective client. |
| * For MIN and MAX votable, returns NULL when the votable |
| * object has been created but no clients have casted their votes or |
| * the last enabled client disables its vote. |
| * For SET_ANY votable it returns NULL too when no clients have casted |
| * their votes. But for SET_ANY since there is no concept of abstaining |
| * from election, the only client that casts a vote or the client that |
| * caused the result to change is returned. |
| */ |
| const char *get_effective_client_locked(struct votable *votable) |
| { |
| if (votable->force_active) |
| return DEBUG_FORCE_CLIENT; |
| |
| return get_client_str(votable, votable->effective_client_id); |
| } |
| |
| const char *get_effective_client(struct votable *votable) |
| { |
| const char *client_str; |
| |
| lock_votable(votable); |
| client_str = get_effective_client_locked(votable); |
| unlock_votable(votable); |
| return client_str; |
| } |
| |
| /** |
| * vote() - |
| * |
| * @votable: the votable object |
| * @client_str: the voting client |
| * @enabled: This provides a means for the client to exclude himself from |
| * election. This clients val (the next argument) will be |
| * considered only when he has enabled his participation. |
| * Note that this takes a differnt meaning for SET_ANY type, as |
| * there is no concept of abstaining from participation. |
| * Enabled is treated as the boolean value the client is voting. |
| * @val: The vote value. This is ignored for SET_ANY votable types. |
| * For MIN, MAX votable types this value is used as the |
| * clients vote value when the enabled is true, this value is |
| * ignored if enabled is false. |
| * |
| * The callback is called only when there is a change in the election results or |
| * if it is the first time someone is voting. |
| * |
| * Returns: |
| * The return from the callback when present and needs to be called |
| * or zero. |
| */ |
| int vote(struct votable *votable, const char *client_str, bool enabled, int val) |
| { |
| int effective_id = -EINVAL; |
| int effective_result; |
| int client_id; |
| int rc = 0; |
| bool similar_vote = false; |
| |
| lock_votable(votable); |
| |
| client_id = get_client_id(votable, client_str); |
| if (client_id < 0) { |
| rc = client_id; |
| goto out; |
| } |
| |
| /* |
| * for SET_ANY the val is to be ignored, set it |
| * to enabled so that the election still works based on |
| * value regardless of the type |
| */ |
| if (votable->type == VOTE_SET_ANY) |
| val = enabled; |
| |
| if ((votable->votes[client_id].enabled == enabled) && |
| (votable->votes[client_id].value == val)) { |
| pr_debug("%s: %s,%d same vote %s of val=%d\n", |
| votable->name, |
| client_str, client_id, |
| enabled ? "on" : "off", |
| val); |
| similar_vote = true; |
| } |
| |
| votable->votes[client_id].enabled = enabled; |
| votable->votes[client_id].value = val; |
| |
| if (similar_vote && votable->voted_on) { |
| pr_debug("%s: %s,%d Ignoring similar vote %s of val=%d\n", |
| votable->name, |
| client_str, client_id, enabled ? "on" : "off", val); |
| goto out; |
| } |
| |
| pr_debug("%s: %s,%d voting %s of val=%d\n", |
| votable->name, |
| client_str, client_id, enabled ? "on" : "off", val); |
| switch (votable->type) { |
| case VOTE_MIN: |
| vote_min(votable, client_id, &effective_result, &effective_id); |
| break; |
| case VOTE_MAX: |
| vote_max(votable, client_id, &effective_result, &effective_id); |
| break; |
| case VOTE_SET_ANY: |
| vote_set_any(votable, client_id, |
| &effective_result, &effective_id); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* |
| * Note that the callback is called with a NULL string and -EINVAL |
| * result when there are no enabled votes |
| */ |
| if (!votable->voted_on |
| || (effective_result != votable->effective_result)) { |
| votable->effective_client_id = effective_id; |
| votable->effective_result = effective_result; |
| pr_debug("%s: effective vote is now %d voted by %s,%d\n", |
| votable->name, effective_result, |
| get_client_str(votable, effective_id), |
| effective_id); |
| if (votable->callback && !votable->force_active) |
| rc = votable->callback(votable, votable->data, |
| effective_result, |
| get_client_str(votable, effective_id)); |
| } |
| |
| votable->voted_on = true; |
| out: |
| unlock_votable(votable); |
| return rc; |
| } |
| |
| int rerun_election(struct votable *votable) |
| { |
| int rc = 0; |
| int effective_result; |
| |
| lock_votable(votable); |
| effective_result = get_effective_result_locked(votable); |
| if (votable->callback) |
| rc = votable->callback(votable, |
| votable->data, |
| effective_result, |
| get_client_str(votable, votable->effective_client_id)); |
| unlock_votable(votable); |
| return rc; |
| } |
| |
| struct votable *find_votable(const char *name) |
| { |
| unsigned long flags; |
| struct votable *v; |
| bool found = false; |
| |
| spin_lock_irqsave(&votable_list_slock, flags); |
| if (list_empty(&votable_list)) |
| goto out; |
| |
| list_for_each_entry(v, &votable_list, list) { |
| if (strcmp(v->name, name) == 0) { |
| found = true; |
| break; |
| } |
| } |
| out: |
| spin_unlock_irqrestore(&votable_list_slock, flags); |
| |
| if (found) |
| return v; |
| else |
| return NULL; |
| } |
| |
| static int force_active_get(void *data, u64 *val) |
| { |
| struct votable *votable = data; |
| |
| *val = votable->force_active; |
| |
| return 0; |
| } |
| |
| static int force_active_set(void *data, u64 val) |
| { |
| struct votable *votable = data; |
| int rc = 0; |
| |
| lock_votable(votable); |
| votable->force_active = !!val; |
| |
| if (!votable->callback) |
| goto out; |
| |
| if (votable->force_active) { |
| rc = votable->callback(votable, votable->data, |
| votable->force_val, |
| DEBUG_FORCE_CLIENT); |
| } else { |
| rc = votable->callback(votable, votable->data, |
| votable->effective_result, |
| get_client_str(votable, votable->effective_client_id)); |
| } |
| out: |
| unlock_votable(votable); |
| return rc; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set, |
| "%lld\n"); |
| |
| static int show_votable_clients(struct seq_file *m, void *data) |
| { |
| struct votable *votable = m->private; |
| int i; |
| char *type_str = "Unkonwn"; |
| const char *effective_client_str; |
| |
| lock_votable(votable); |
| |
| for (i = 0; i < votable->num_clients; i++) { |
| if (votable->client_strs[i]) { |
| seq_printf(m, "%s: %s:\t\t\ten=%d v=%d\n", |
| votable->name, |
| votable->client_strs[i], |
| votable->votes[i].enabled, |
| votable->votes[i].value); |
| } |
| } |
| |
| switch (votable->type) { |
| case VOTE_MIN: |
| type_str = "Min"; |
| break; |
| case VOTE_MAX: |
| type_str = "Max"; |
| break; |
| case VOTE_SET_ANY: |
| type_str = "Set_any"; |
| break; |
| } |
| |
| effective_client_str = get_effective_client_locked(votable); |
| seq_printf(m, "%s: effective=%s type=%s v=%d\n", |
| votable->name, |
| effective_client_str ? effective_client_str : "none", |
| type_str, |
| get_effective_result_locked(votable)); |
| unlock_votable(votable); |
| |
| return 0; |
| } |
| |
| static int votable_status_open(struct inode *inode, struct file *file) |
| { |
| struct votable *votable = inode->i_private; |
| |
| return single_open(file, show_votable_clients, votable); |
| } |
| |
| static const struct file_operations votable_status_ops = { |
| .owner = THIS_MODULE, |
| .open = votable_status_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| struct votable *create_votable(const char *name, |
| int votable_type, |
| int (*callback)(struct votable *votable, |
| void *data, |
| int effective_result, |
| const char *effective_client), |
| void *data) |
| { |
| struct votable *votable; |
| unsigned long flags; |
| |
| votable = find_votable(name); |
| if (votable) |
| return ERR_PTR(-EEXIST); |
| |
| if (debug_root == NULL) { |
| debug_root = debugfs_create_dir("pmic-votable", NULL); |
| if (!debug_root) { |
| pr_err("Couldn't create debug dir\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| } |
| |
| if (votable_type >= NUM_VOTABLE_TYPES) { |
| pr_err("Invalid votable_type specified for voter\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| votable = kzalloc(sizeof(struct votable), GFP_KERNEL); |
| if (!votable) |
| return ERR_PTR(-ENOMEM); |
| |
| votable->name = kstrdup(name, GFP_KERNEL); |
| if (!votable->name) { |
| kfree(votable); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| votable->num_clients = NUM_MAX_CLIENTS; |
| votable->callback = callback; |
| votable->type = votable_type; |
| votable->data = data; |
| mutex_init(&votable->vote_lock); |
| |
| /* |
| * Because effective_result and client states are invalid |
| * before the first vote, initialize them to -EINVAL |
| */ |
| votable->effective_result = -EINVAL; |
| if (votable->type == VOTE_SET_ANY) |
| votable->effective_result = 0; |
| votable->effective_client_id = -EINVAL; |
| |
| spin_lock_irqsave(&votable_list_slock, flags); |
| list_add(&votable->list, &votable_list); |
| spin_unlock_irqrestore(&votable_list_slock, flags); |
| |
| votable->root = debugfs_create_dir(name, debug_root); |
| if (!votable->root) { |
| pr_err("Couldn't create debug dir %s\n", name); |
| kfree(votable->name); |
| kfree(votable); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| votable->status_ent = debugfs_create_file("status", S_IFREG | 0444, |
| votable->root, votable, |
| &votable_status_ops); |
| if (!votable->status_ent) { |
| pr_err("Couldn't create status dbg file for %s\n", name); |
| debugfs_remove_recursive(votable->root); |
| kfree(votable->name); |
| kfree(votable); |
| return ERR_PTR(-EEXIST); |
| } |
| |
| votable->force_val_ent = debugfs_create_u32("force_val", |
| S_IFREG | 0644, |
| votable->root, |
| &(votable->force_val)); |
| |
| if (!votable->force_val_ent) { |
| pr_err("Couldn't create force_val dbg file for %s\n", name); |
| debugfs_remove_recursive(votable->root); |
| kfree(votable->name); |
| kfree(votable); |
| return ERR_PTR(-EEXIST); |
| } |
| |
| votable->force_active_ent = debugfs_create_file("force_active", |
| S_IFREG | 0444, |
| votable->root, votable, |
| &votable_force_ops); |
| if (!votable->force_active_ent) { |
| pr_err("Couldn't create force_active dbg file for %s\n", name); |
| debugfs_remove_recursive(votable->root); |
| kfree(votable->name); |
| kfree(votable); |
| return ERR_PTR(-EEXIST); |
| } |
| |
| return votable; |
| } |
| |
| void destroy_votable(struct votable *votable) |
| { |
| unsigned long flags; |
| int i; |
| |
| if (!votable) |
| return; |
| |
| spin_lock_irqsave(&votable_list_slock, flags); |
| list_del(&votable->list); |
| spin_unlock_irqrestore(&votable_list_slock, flags); |
| |
| debugfs_remove_recursive(votable->root); |
| |
| for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) |
| kfree(votable->client_strs[i]); |
| |
| kfree(votable->name); |
| kfree(votable); |
| } |