/* Copyright (c) 2015-2016 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 #include #include #include #include #include #include #include "pmic-voter.h" #define NUM_MAX_CLIENTS 8 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 *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); } /** * 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) { 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; } const char *get_effective_client_locked(struct votable *votable) { 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 %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 Similar vote %s of %d\n", votable->name, client_str, client_id, enabled ? "on" : "off", val); goto out; } pr_debug("%s: %s,%d voting %s of %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) 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; lock_votable(votable); if (votable->callback) rc = votable->callback(votable, votable->data, votable->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 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); seq_printf(m, "%s:\n", votable->name); seq_puts(m, "Clients:\n"); for (i = 0; i < votable->num_clients; i++) { if (votable->client_strs[i]) { seq_printf(m, "%-15s:\t\ten=%d\t\tv=%d\n", 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; } seq_printf(m, "Type: %s\n", type_str); seq_puts(m, "Effective:\n"); effective_client_str = get_effective_client_locked(votable); seq_printf(m, "%-15s:\t\tv=%d\n", effective_client_str ? effective_client_str : "none", get_effective_result_locked(votable)); unlock_votable(votable); return 0; } static int votable_debugfs_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_debugfs_ops = { .owner = THIS_MODULE, .open = votable_debugfs_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->ent = debugfs_create_file(name, S_IFREG | S_IRUGO, debug_root, votable, &votable_debugfs_ops); if (!votable->ent) { pr_err("Couldn't create %s debug file\n", name); 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(votable->ent); for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) kfree(votable->client_strs[i]); kfree(votable->name); kfree(votable); }