diff options
Diffstat (limited to 'drivers/soc/qcom/remoteqdss.c')
-rw-r--r-- | drivers/soc/qcom/remoteqdss.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/drivers/soc/qcom/remoteqdss.c b/drivers/soc/qcom/remoteqdss.c new file mode 100644 index 000000000000..5e2a5babdcc8 --- /dev/null +++ b/drivers/soc/qcom/remoteqdss.c @@ -0,0 +1,448 @@ +/* + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <soc/qcom/scm.h> +#include <linux/debugfs.h> +#include <linux/ratelimit.h> +#include <linux/dma-mapping.h> + +#define REMOTEQDSS_FLAG_QUIET (BIT(0)) + +static unsigned long remoteqdss_dbg_flags; +module_param_named(dbg_flags, remoteqdss_dbg_flags, ulong, 0644); + +static struct dentry *remoteqdss_dir; + +#define REMOTEQDSS_ERR(fmt, ...) \ + pr_debug("%s: " fmt, __func__, ## __VA_ARGS__) + +#define REMOTEQDSS_ERR_CALLER(fmt, caller, ...) \ + pr_debug("%pf: " fmt, caller, ## __VA_ARGS__) + +struct qdss_msg_translation { + u64 val; + char *msg; +}; + +/* + * id Unique identifier + * sw_entity_group Array index + * sw_event_group Array index + * dir Parent debugfs directory + */ +struct remoteqdss_data { + uint32_t id; + uint32_t sw_entity_group; + uint32_t sw_event_group; + struct dentry *dir; +}; + +static struct device dma_dev; + +/* Allowed message formats */ + +enum remoteqdss_cmd_id { + CMD_ID_QUERY_SWEVENT_TAG, + CMD_ID_FILTER_SWTRACE_STATE, + CMD_ID_QUERY_SWTRACE_STATE, + CMD_ID_FILTER_SWEVENT, + CMD_ID_QUERY_SWEVENT, + CMD_ID_FILTER_SWENTITY, + CMD_ID_QUERY_SWENTITY, +}; + +struct remoteqdss_header_fmt { + uint32_t subsys_id; + uint32_t cmd_id; +}; + +struct remoteqdss_filter_swtrace_state_fmt { + struct remoteqdss_header_fmt h; + uint32_t state; +}; + +struct remoteqdss_filter_swevent_fmt { + struct remoteqdss_header_fmt h; + uint32_t event_group; + uint32_t event_mask; +}; + +struct remoteqdss_query_swevent_fmt { + struct remoteqdss_header_fmt h; + uint32_t event_group; +}; + +struct remoteqdss_filter_swentity_fmt { + struct remoteqdss_header_fmt h; + uint32_t entity_group; + uint32_t entity_mask; +}; + +struct remoteqdss_query_swentity_fmt { + struct remoteqdss_header_fmt h; + uint32_t entity_group; +}; + +/* msgs is a null terminated array */ +static void remoteqdss_err_translation(struct qdss_msg_translation *msgs, + u64 err, const void *caller) +{ + static DEFINE_RATELIMIT_STATE(rl, 5 * HZ, 2); + struct qdss_msg_translation *msg; + + if (!err) + return; + + if (remoteqdss_dbg_flags & REMOTEQDSS_FLAG_QUIET) + return; + + for (msg = msgs; msg->msg; msg++) { + if (err == msg->val && __ratelimit(&rl)) { + REMOTEQDSS_ERR_CALLER("0x%llx: %s\n", caller, err, + msg->msg); + return; + } + } + + REMOTEQDSS_ERR_CALLER("Error 0x%llx\n", caller, err); +} + +/* Shared across all remoteqdss scm functions */ +#define SCM_CMD_ID (0x1) + +/* Response Values */ +#define SCM_CMD_FAIL (0x80) +#define SCM_QDSS_UNAVAILABLE (0x81) +#define SCM_UNINITIALIZED (0x82) +#define SCM_BAD_ARG (0x83) +#define SCM_BAD_SUBSYS (0x85) + +static struct qdss_msg_translation remoteqdss_scm_msgs[] = { + {SCM_CMD_FAIL, + "Command failed"}, + {SCM_QDSS_UNAVAILABLE, + "QDSS not available or cannot turn QDSS (clock) on"}, + {SCM_UNINITIALIZED, + "Tracer not initialized or unable to initialize"}, + {SCM_BAD_ARG, + "Invalid parameter value"}, + {SCM_BAD_SUBSYS, + "Incorrect subsys ID"}, + {} +}; + +static struct remoteqdss_data *create_remoteqdss_data(u32 id) +{ + struct remoteqdss_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->id = id; + return data; +} + +static void free_remoteqdss_data(struct remoteqdss_data *data) +{ + kfree(data); +} + +static int remoteqdss_do_scm_call(struct scm_desc *desc, + dma_addr_t addr, size_t size, const void *caller) +{ + int ret; + + memset(desc, 0, sizeof(*desc)); + desc->args[0] = dma_to_phys(NULL, addr); + desc->args[1] = size; + desc->arginfo = SCM_ARGS(2, SCM_RO, SCM_VAL); + + ret = scm_call2( + SCM_SIP_FNID(SCM_SVC_QDSS, SCM_CMD_ID), + desc); + if (ret) + return ret; + + remoteqdss_err_translation(remoteqdss_scm_msgs, desc->ret[0], caller); + ret = desc->ret[0] ? -EINVAL : 0; + return ret; +} + +static int remoteqdss_scm_query_swtrace(void *priv, u64 *val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_header_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->subsys_id = data->id; + fmt->cmd_id = CMD_ID_QUERY_SWTRACE_STATE; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + *val = desc.ret[1]; + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +static int remoteqdss_scm_filter_swtrace(void *priv, u64 val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_filter_swtrace_state_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->h.subsys_id = data->id; + fmt->h.cmd_id = CMD_ID_FILTER_SWTRACE_STATE; + fmt->state = (uint32_t)val; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_sw_trace_output, + remoteqdss_scm_query_swtrace, + remoteqdss_scm_filter_swtrace, + "0x%llx\n"); + +static int remoteqdss_scm_query_tag(void *priv, u64 *val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_header_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->subsys_id = data->id; + fmt->cmd_id = CMD_ID_QUERY_SWEVENT_TAG; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + *val = desc.ret[1]; + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_tag, + remoteqdss_scm_query_tag, + NULL, + "0x%llx\n"); + +static int remoteqdss_scm_query_swevent(void *priv, u64 *val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_query_swevent_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->h.subsys_id = data->id; + fmt->h.cmd_id = CMD_ID_QUERY_SWEVENT; + fmt->event_group = data->sw_event_group; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + *val = desc.ret[1]; + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +static int remoteqdss_scm_filter_swevent(void *priv, u64 val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_filter_swevent_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->h.subsys_id = data->id; + fmt->h.cmd_id = CMD_ID_FILTER_SWEVENT; + fmt->event_group = data->sw_event_group; + fmt->event_mask = (uint32_t)val; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_swevent, + remoteqdss_scm_query_swevent, + remoteqdss_scm_filter_swevent, + "0x%llx\n"); + +static int remoteqdss_scm_query_swentity(void *priv, u64 *val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_query_swentity_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->h.subsys_id = data->id; + fmt->h.cmd_id = CMD_ID_QUERY_SWENTITY; + fmt->entity_group = data->sw_entity_group; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + *val = desc.ret[1]; + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +static int remoteqdss_scm_filter_swentity(void *priv, u64 val) +{ + struct remoteqdss_data *data = priv; + int ret; + struct scm_desc desc; + struct remoteqdss_filter_swentity_fmt *fmt; + dma_addr_t addr; + + fmt = dma_alloc_coherent(&dma_dev, sizeof(*fmt), &addr, GFP_KERNEL); + if (!fmt) + return -ENOMEM; + fmt->h.subsys_id = data->id; + fmt->h.cmd_id = CMD_ID_FILTER_SWENTITY; + fmt->entity_group = data->sw_entity_group; + fmt->entity_mask = (uint32_t)val; + + ret = remoteqdss_do_scm_call(&desc, addr, sizeof(*fmt), + __builtin_return_address(0)); + + dma_free_coherent(&dma_dev, sizeof(*fmt), fmt, addr); + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_swentity, + remoteqdss_scm_query_swentity, + remoteqdss_scm_filter_swentity, + "0x%llx\n"); + +static void __init enumerate_scm_devices(struct dentry *parent) +{ + u64 unused; + int ret; + struct remoteqdss_data *data; + struct dentry *dentry; + + if (!is_scm_armv8()) + return; + + data = create_remoteqdss_data(0); + if (!data) + return; + + /* Assume failure means device not present */ + ret = remoteqdss_scm_query_swtrace(data, &unused); + if (ret) + goto out; + + data->dir = debugfs_create_dir("tz", parent); + if (IS_ERR_OR_NULL(data->dir)) + goto out; + + dentry = debugfs_create_file("sw_trace_output", S_IRUGO | S_IWUSR, + data->dir, data, &fops_sw_trace_output); + if (IS_ERR_OR_NULL(dentry)) + goto out; + + dentry = debugfs_create_u32("sw_entity_group", S_IRUGO | S_IWUSR, + data->dir, &data->sw_entity_group); + if (IS_ERR_OR_NULL(dentry)) + goto out; + + dentry = debugfs_create_u32("sw_event_group", S_IRUGO | S_IWUSR, + data->dir, &data->sw_event_group); + if (IS_ERR_OR_NULL(dentry)) + goto out; + + dentry = debugfs_create_file("tag", S_IRUGO, + data->dir, data, &fops_tag); + if (IS_ERR_OR_NULL(dentry)) + goto out; + + dentry = debugfs_create_file("swevent", S_IRUGO | S_IWUSR, + data->dir, data, &fops_swevent); + if (IS_ERR_OR_NULL(dentry)) + goto out; + + dentry = debugfs_create_file("swentity", S_IRUGO | S_IWUSR, + data->dir, data, &fops_swentity); + if (IS_ERR_OR_NULL(dentry)) + goto out; + + return; + +out: + debugfs_remove_recursive(data->dir); + free_remoteqdss_data(data); +} + +static int __init remoteqdss_init(void) +{ + unsigned long old_flags = remoteqdss_dbg_flags; + int ret; + + /* Set up DMA */ + arch_setup_dma_ops(&dma_dev, 0, U64_MAX, NULL, false); + ret = dma_coerce_mask_and_coherent(&dma_dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + /* + * disable normal error messages while checking + * if support is present. + */ + remoteqdss_dbg_flags |= REMOTEQDSS_FLAG_QUIET; + + remoteqdss_dir = debugfs_create_dir("remoteqdss", NULL); + if (!remoteqdss_dir) + return 0; + + enumerate_scm_devices(remoteqdss_dir); + + remoteqdss_dbg_flags = old_flags; + return 0; +} +late_initcall(remoteqdss_init); |