diff options
Diffstat (limited to 'drivers/esoc/esoc-mdm-dbg-eng.c')
| -rw-r--r-- | drivers/esoc/esoc-mdm-dbg-eng.c | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/drivers/esoc/esoc-mdm-dbg-eng.c b/drivers/esoc/esoc-mdm-dbg-eng.c new file mode 100644 index 000000000000..d4ad25123565 --- /dev/null +++ b/drivers/esoc/esoc-mdm-dbg-eng.c @@ -0,0 +1,369 @@ +/* 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/atomic.h> +#include <linux/device.h> +#include "esoc.h" + +/* + * cmd_mask : Specifies if a command/notifier is masked, and + * whats the trigger value for mask to take effect. + * @mask_trigger: trigger value for mask. + * @mask: boolean to determine if command should be masked. + */ +struct esoc_mask { + atomic_t mask_trigger; + bool mask; +}; + +/* + * manual_to_esoc_cmd: Converts a user provided command + * to a corresponding esoc command. + * @cmd: ESOC command + * @manual_cmd: user specified command string. + */ +struct manual_to_esoc_cmd { + unsigned int cmd; + char manual_cmd[20]; +}; + +/* + * manual_to_esoc_notify: Converts a user provided notification + * to corresponding esoc notification for Primary SOC. + * @notfication: ESOC notification. + * @manual_notifier: user specified notification string. + */ +struct manual_to_esoc_notify { + unsigned int notify; + char manual_notify[20]; +}; + +static const struct manual_to_esoc_cmd cmd_map[] = { + { + .cmd = ESOC_PWR_ON, + .manual_cmd = "PON", + }, + { + .cmd = ESOC_PREPARE_DEBUG, + .manual_cmd = "ENTER_DLOAD", + }, + { .cmd = ESOC_PWR_OFF, + .manual_cmd = "POFF", + }, + { + .cmd = ESOC_FORCE_PWR_OFF, + .manual_cmd = "FORCE_POFF", + }, +}; + +static struct esoc_mask cmd_mask[] = { + [ESOC_PWR_ON] = { + .mask = false, + .mask_trigger = ATOMIC_INIT(1), + }, + [ESOC_PREPARE_DEBUG] = { + .mask = false, + .mask_trigger = ATOMIC_INIT(0), + }, + [ESOC_PWR_OFF] = { + .mask = false, + .mask_trigger = ATOMIC_INIT(0), + }, + [ESOC_FORCE_PWR_OFF] = { + .mask = false, + .mask_trigger = ATOMIC_INIT(0), + }, +}; + +static const struct manual_to_esoc_notify notify_map[] = { + { + .notify = ESOC_PRIMARY_REBOOT, + .manual_notify = "REBOOT", + }, + { + .notify = ESOC_PRIMARY_CRASH, + .manual_notify = "PANIC", + }, +}; + +static struct esoc_mask notify_mask[] = { + [ESOC_PRIMARY_REBOOT] = { + .mask = false, + .mask_trigger = ATOMIC_INIT(0), + }, + [ESOC_PRIMARY_CRASH] = { + .mask = false, + .mask_trigger = ATOMIC_INIT(0), + }, +}; + +bool dbg_check_cmd_mask(unsigned int cmd) +{ + pr_debug("command to mask %d\n", cmd); + if (cmd_mask[cmd].mask) + return atomic_add_negative(-1, &cmd_mask[cmd].mask_trigger); + else + return false; +} +EXPORT_SYMBOL(dbg_check_cmd_mask); + +bool dbg_check_notify_mask(unsigned int notify) +{ + pr_debug("notifier to mask %d\n", notify); + if (notify_mask[notify].mask) + return atomic_add_negative(-1, + ¬ify_mask[notify].mask_trigger); + else + return false; +} +EXPORT_SYMBOL(dbg_check_notify_mask); +/* + * Create driver attributes that let you mask + * specific commands. + */ +static ssize_t cmd_mask_store(struct device_driver *drv, const char *buf, + size_t count) +{ + unsigned int cmd, i; + + pr_debug("user input command %s", buf); + for (i = 0; i < ARRAY_SIZE(cmd_map); i++) { + if (!strcmp(cmd_map[i].manual_cmd, buf)) { + /* + * Map manual command string to ESOC command + * set mask for ESOC command + */ + cmd = cmd_map[i].cmd; + cmd_mask[cmd].mask = true; + pr_debug("Setting mask for manual command %s\n", + buf); + break; + } + } + if (i >= ARRAY_SIZE(cmd_map)) + pr_err("invalid command specified\n"); + return count; +} +static DRIVER_ATTR(command_mask, S_IWUSR, NULL, cmd_mask_store); + +static ssize_t notifier_mask_store(struct device_driver *drv, const char *buf, + size_t count) +{ + unsigned int notify, i; + + pr_debug("user input notifier %s", buf); + for (i = 0; i < ARRAY_SIZE(notify_map); i++) { + if (!strcmp(buf, notify_map[i].manual_notify)) { + /* + * Map manual notifier string to primary soc + * notifier. Also set mask for the notifier. + */ + notify = notify_map[i].notify; + notify_mask[notify].mask = true; + pr_debug("Setting mask for manual notification %s\n", + buf); + break; + } + } + if (i >= ARRAY_SIZE(notify_map)) + pr_err("invalid notifier specified\n"); + return count; +} +static DRIVER_ATTR(notifier_mask, S_IWUSR, NULL, notifier_mask_store); + +#ifdef CONFIG_MDM_DBG_REQ_ENG +static struct esoc_clink *dbg_clink; +/* Last recorded request from esoc */ +static enum esoc_req last_req; +static DEFINE_SPINLOCK(req_lock); +/* + * esoc_to_user: Conversion of esoc ids to user visible strings + * id: esoc request, command, notifier, event id + * str: string equivalent of the above + */ +struct esoc_to_user { + unsigned int id; + char str[20]; +}; + +static struct esoc_to_user in_to_resp[] = { + { + .id = ESOC_IMG_XFER_DONE, + .str = "XFER_DONE", + }, + { + .id = ESOC_BOOT_DONE, + .str = "BOOT_DONE", + }, + { + .id = ESOC_BOOT_FAIL, + .str = "BOOT_FAIL", + }, + { + .id = ESOC_IMG_XFER_RETRY, + .str = "XFER_RETRY", + }, + { .id = ESOC_IMG_XFER_FAIL, + .str = "XFER_FAIL", + }, + { + .id = ESOC_UPGRADE_AVAILABLE, + .str = "UPGRADE", + }, + { .id = ESOC_DEBUG_DONE, + .str = "DEBUG_DONE", + }, + { + .id = ESOC_DEBUG_FAIL, + .str = "DEBUG_FAIL", + }, +}; + +static struct esoc_to_user req_to_str[] = { + { + .id = ESOC_REQ_IMG, + .str = "REQ_IMG", + }, + { + .id = ESOC_REQ_DEBUG, + .str = "REQ_DEBUG", + }, + { + .id = ESOC_REQ_SHUTDOWN, + .str = "REQ_SHUTDOWN", + }, +}; + +static ssize_t req_eng_resp_store(struct device_driver *drv, const char *buf, + size_t count) +{ + unsigned int i; + const struct esoc_clink_ops *const clink_ops = dbg_clink->clink_ops; + + dev_dbg(&dbg_clink->dev, "user input req eng response %s\n", buf); + for (i = 0; i < ARRAY_SIZE(in_to_resp); i++) { + size_t len1 = strlen(buf); + size_t len2 = strlen(in_to_resp[i].str); + + if (len1 == len2 && !strcmp(buf, in_to_resp[i].str)) { + clink_ops->notify(in_to_resp[i].id, dbg_clink); + break; + } + } + if (i > ARRAY_SIZE(in_to_resp)) + dev_err(&dbg_clink->dev, "Invalid resp %s, specified\n", buf); + return count; +} + +static DRIVER_ATTR(req_eng_resp, S_IWUSR, NULL, req_eng_resp_store); + +static ssize_t last_esoc_req_show(struct device_driver *drv, char *buf) +{ + unsigned int i; + unsigned long flags; + size_t count; + + spin_lock_irqsave(&req_lock, flags); + for (i = 0; i < ARRAY_SIZE(req_to_str); i++) { + if (last_req == req_to_str[i].id) { + count = snprintf(buf, PAGE_SIZE, "%s\n", + req_to_str[i].str); + break; + } + } + spin_unlock_irqrestore(&req_lock, flags); + return count; +} +static DRIVER_ATTR(last_esoc_req, S_IRUSR, last_esoc_req_show, NULL); + +static void esoc_handle_req(enum esoc_req req, struct esoc_eng *eng) +{ + unsigned long flags; + + spin_lock_irqsave(&req_lock, flags); + last_req = req; + spin_unlock_irqrestore(&req_lock, flags); +} + +static void esoc_handle_evt(enum esoc_evt evt, struct esoc_eng *eng) +{ +} + +static struct esoc_eng dbg_req_eng = { + .handle_clink_req = esoc_handle_req, + .handle_clink_evt = esoc_handle_evt, +}; + +int register_dbg_req_eng(struct esoc_clink *clink, + struct device_driver *drv) +{ + int ret; + + dbg_clink = clink; + ret = driver_create_file(drv, &driver_attr_req_eng_resp); + if (ret) + return ret; + ret = driver_create_file(drv, &driver_attr_last_esoc_req); + if (ret) { + dev_err(&clink->dev, "Unable to create last esoc req\n"); + goto last_req_err; + } + ret = esoc_clink_register_req_eng(clink, &dbg_req_eng); + if (ret) { + pr_err("Unable to register req eng\n"); + goto req_eng_fail; + } + spin_lock_init(&req_lock); + return 0; +last_req_err: + driver_remove_file(drv, &driver_attr_last_esoc_req); +req_eng_fail: + driver_remove_file(drv, &driver_attr_req_eng_resp); + return ret; +} +#else +int register_dbg_req_eng(struct esoc_clink *clink, struct device_driver *d) +{ + return 0; +} +#endif + +int mdm_dbg_eng_init(struct esoc_drv *esoc_drv, + struct esoc_clink *clink) +{ + int ret; + struct device_driver *drv = &esoc_drv->driver; + + ret = driver_create_file(drv, &driver_attr_command_mask); + if (ret) { + pr_err("Unable to create command mask file\n"); + goto cmd_mask_err; + } + ret = driver_create_file(drv, &driver_attr_notifier_mask); + if (ret) { + pr_err("Unable to create notify mask file\n"); + goto notify_mask_err; + } + ret = register_dbg_req_eng(clink, drv); + if (ret) { + pr_err("Failed to register esoc dbg req eng\n"); + goto dbg_req_fail; + } + return 0; +dbg_req_fail: + driver_remove_file(drv, &driver_attr_notifier_mask); +notify_mask_err: + driver_remove_file(drv, &driver_attr_command_mask); +cmd_mask_err: + return ret; +} +EXPORT_SYMBOL(mdm_dbg_eng_init); +MODULE_LICENSE("GPL V2"); |
