summaryrefslogtreecommitdiff
path: root/drivers/misc/profiler.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/profiler.c')
-rw-r--r--drivers/misc/profiler.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/drivers/misc/profiler.c b/drivers/misc/profiler.c
new file mode 100644
index 000000000000..a2887fcefbab
--- /dev/null
+++ b/drivers/misc/profiler.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 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.
+ */
+
+#define pr_fmt(fmt) "PROFILER: %s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <soc/qcom/scm.h>
+#include <soc/qcom/socinfo.h>
+#include <asm/cacheflush.h>
+#include <linux/delay.h>
+#include <soc/qcom/profiler.h>
+
+#include <linux/compat.h>
+
+#define PROFILER_DEV "profiler"
+
+static struct class *driver_class;
+static dev_t profiler_device_no;
+
+struct profiler_control {
+ struct device *pdev;
+ struct cdev cdev;
+};
+
+static struct profiler_control profiler;
+
+struct profiler_dev_handle {
+ bool released;
+ int abort;
+ atomic_t ioctl_count;
+};
+
+
+static int profiler_scm_call2(uint32_t svc_id, uint32_t tz_cmd_id,
+ const void *req_buf, void *resp_buf)
+{
+ int ret = 0;
+ uint32_t qseos_cmd_id = 0;
+ struct scm_desc desc = {0};
+
+ if (!req_buf || !resp_buf) {
+ pr_err("Invalid buffer pointer\n");
+ return -EINVAL;
+ }
+ qseos_cmd_id = *(uint32_t *)req_buf;
+
+ switch (svc_id) {
+
+ case SCM_SVC_BW:
+ switch (qseos_cmd_id) {
+ case TZ_BW_SVC_START_ID:
+ case TZ_BW_SVC_GET_ID:
+ case TZ_BW_SVC_STOP_ID:
+ /* Send the command to TZ */
+ desc.arginfo = SCM_ARGS(4, SCM_RW, SCM_VAL,
+ SCM_RW, SCM_VAL);
+ desc.args[0] = virt_to_phys(&
+ (((struct tz_bw_svc_buf *)
+ req_buf)->bwreq));
+ desc.args[1] = ((struct tz_bw_svc_buf *)
+ req_buf)->req_size;
+ desc.args[2] = virt_to_phys(&
+ ((struct tz_bw_svc_buf *)
+ req_buf)->bwresp);
+ desc.args[3] = sizeof(struct tz_bw_svc_resp);
+
+ ret = scm_call2(SCM_SIP_FNID(SCM_SVC_INFO,
+ TZ_SVC_BW_PROF_ID), &desc);
+ break;
+ default:
+ pr_err("cmd_id %d is not supported by scm_call2.\n",
+ qseos_cmd_id);
+ ret = -EINVAL;
+ } /*end of switch (qsee_cmd_id) */
+ break;
+ default:
+ pr_err("svc_id 0x%x is not supported by armv8 scm_call2.\n",
+ svc_id);
+ ret = -EINVAL;
+ break;
+ } /*end of switch svc_id */
+ return ret;
+}
+
+
+static int profiler_scm_call(u32 svc_id, u32 tz_cmd_id, const void *cmd_buf,
+ size_t cmd_len, void *resp_buf, size_t resp_len)
+{
+ if (!is_scm_armv8())
+ return scm_call(svc_id, tz_cmd_id, cmd_buf, cmd_len,
+ resp_buf, resp_len);
+ else
+ return profiler_scm_call2(svc_id, tz_cmd_id, cmd_buf, resp_buf);
+}
+
+static int bw_profiling_command(void *req)
+{
+ struct tz_bw_svc_resp *bw_resp = NULL;
+ uint32_t cmd_id = 0;
+ int ret;
+
+ cmd_id = *(uint32_t *)req;
+ bw_resp = &((struct tz_bw_svc_buf *)req)->bwresp;
+ /* Flush buffers from cache to memory. */
+ dmac_flush_range(req, req +
+ PAGE_ALIGN(sizeof(union tz_bw_svc_req)));
+ dmac_flush_range((void *)bw_resp, ((void *)bw_resp) +
+ sizeof(struct tz_bw_svc_resp));
+ ret = profiler_scm_call(SCM_SVC_BW, TZ_SVC_BW_PROF_ID, req,
+ sizeof(struct tz_bw_svc_buf),
+ bw_resp, sizeof(struct tz_bw_svc_resp));
+ if (ret) {
+ pr_err("profiler_scm_call failed with err: %d\n", ret);
+ return -EINVAL;
+ }
+ /* Invalidate cache. */
+ dmac_inv_range((void *)bw_resp, ((void *)bw_resp) +
+ sizeof(struct tz_bw_svc_resp));
+ /* Verify cmd id and Check that request succeeded.*/
+ if ((bw_resp->status != E_BW_SUCCESS) ||
+ (cmd_id != bw_resp->cmd_id)) {
+ ret = -1;
+ pr_err("Status: %d,Cmd: %d\n",
+ bw_resp->status,
+ bw_resp->cmd_id);
+ }
+ return ret;
+}
+
+static int bw_profiling_start(struct tz_bw_svc_buf *bwbuf)
+{
+ struct tz_bw_svc_start_req *bwstartreq = NULL;
+
+ bwstartreq = (struct tz_bw_svc_start_req *) &bwbuf->bwreq;
+ /* Populate request data */
+ bwstartreq->cmd_id = TZ_BW_SVC_START_ID;
+ bwstartreq->version = TZ_BW_SVC_VERSION;
+ bwbuf->req_size = sizeof(struct tz_bw_svc_start_req);
+ return bw_profiling_command(bwbuf);
+}
+
+static int bw_profiling_get(void __user *argp, struct tz_bw_svc_buf *bwbuf)
+{
+ struct tz_bw_svc_get_req *bwgetreq = NULL;
+ int ret;
+ char *buf = NULL;
+ const int numberofregs = 3;
+ struct profiler_bw_cntrs_req cnt_buf;
+
+ bwgetreq = (struct tz_bw_svc_get_req *) &bwbuf->bwreq;
+ /* Allocate memory for get buffer */
+ buf = kzalloc(PAGE_ALIGN(numberofregs * sizeof(uint32_t)), GFP_KERNEL);
+ if (buf == NULL) {
+ ret = -ENOMEM;
+ pr_err(" Failed to allocate memory\n");
+ return ret;
+ }
+ /* Populate request data */
+ bwgetreq->cmd_id = TZ_BW_SVC_GET_ID;
+ bwgetreq->buf_ptr = (uint64_t) virt_to_phys(buf);
+ bwgetreq->buf_size = numberofregs * sizeof(uint32_t);
+ bwbuf->req_size = sizeof(struct tz_bw_svc_get_req);
+ dmac_flush_range(buf, ((void *)buf) + PAGE_ALIGN(bwgetreq->buf_size));
+ ret = bw_profiling_command(bwbuf);
+ if (ret) {
+ pr_err("bw_profiling_command failed\n");
+ return ret;
+ }
+ dmac_inv_range(buf, ((void *)buf) + PAGE_ALIGN(bwgetreq->buf_size));
+ cnt_buf.total = *(uint32_t *) (buf + 0 * sizeof(uint32_t));
+ cnt_buf.cpu = *(uint32_t *) (buf + 1 * sizeof(uint32_t));
+ cnt_buf.gpu = *(uint32_t *) (buf + 2 * sizeof(uint32_t));
+ if (copy_to_user(argp, &cnt_buf, sizeof(struct profiler_bw_cntrs_req)))
+ pr_err("copy_to_user failed\n");
+ /* Free memory for response */
+ if (buf != NULL) {
+ kfree(buf);
+ buf = NULL;
+ }
+ return ret;
+}
+
+static int bw_profiling_stop(struct tz_bw_svc_buf *bwbuf)
+{
+ struct tz_bw_svc_stop_req *bwstopreq = NULL;
+
+ bwstopreq = (struct tz_bw_svc_stop_req *) &bwbuf->bwreq;
+ /* Populate request data */
+ bwstopreq->cmd_id = TZ_BW_SVC_STOP_ID;
+ bwbuf->req_size = sizeof(struct tz_bw_svc_stop_req);
+ return bw_profiling_command(bwbuf);
+}
+
+
+static int profiler_get_bw_info(void __user *argp)
+{
+ int ret = 0;
+ struct tz_bw_svc_buf *bwbuf = NULL;
+ struct profiler_bw_cntrs_req cnt_buf;
+
+ ret = copy_from_user(&cnt_buf, argp,
+ sizeof(struct profiler_bw_cntrs_req));
+ if (ret)
+ return ret;
+ /* Allocate memory for request */
+ bwbuf = kzalloc(PAGE_ALIGN(sizeof(struct tz_bw_svc_buf)), GFP_KERNEL);
+ if (bwbuf == NULL)
+ return -ENOMEM;
+ switch (cnt_buf.cmd) {
+ case TZ_BW_SVC_START_ID:
+ ret = bw_profiling_start(bwbuf);
+ if (ret)
+ pr_err("bw_profiling_start Failed with ret: %d\n", ret);
+ break;
+ case TZ_BW_SVC_GET_ID:
+ ret = bw_profiling_get(argp, bwbuf);
+ if (ret)
+ pr_err("bw_profiling_get Failed with ret: %d\n", ret);
+ break;
+ case TZ_BW_SVC_STOP_ID:
+ ret = bw_profiling_stop(bwbuf);
+ if (ret)
+ pr_err("bw_profiling_stop Failed with ret: %d\n", ret);
+ break;
+ default:
+ pr_err("Invalid IOCTL: 0x%x\n", cnt_buf.cmd);
+ ret = -EINVAL;
+ }
+ /* Free memory for command */
+ if (bwbuf != NULL) {
+ kfree(bwbuf);
+ bwbuf = NULL;
+ }
+ return ret;
+}
+
+static int profiler_open(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+ struct profiler_dev_handle *data;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ file->private_data = data;
+ data->abort = 0;
+ data->released = false;
+ atomic_set(&data->ioctl_count, 0);
+ return ret;
+}
+
+static int compat_get_profiler_bw_info(
+ struct compat_profiler_bw_cntrs_req __user *data32,
+ struct profiler_bw_cntrs_req __user *data)
+{
+ compat_uint_t total;
+ compat_uint_t cpu;
+ compat_uint_t gpu;
+ compat_uint_t cmd;
+ int err;
+
+ err = get_user(total, &data32->total);
+ err |= put_user(total, &data->total);
+ err |= get_user(gpu, &data32->gpu);
+ err |= put_user(gpu, &data->gpu);
+ err |= get_user(cpu, &data32->cpu);
+ err |= put_user(cpu, &data->cpu);
+ err |= get_user(cmd, &data32->cmd);
+ err |= put_user(cmd, &data->cmd);
+ return err;
+}
+
+static int compat_put_profiler_bw_info(
+ struct compat_profiler_bw_cntrs_req __user *data32,
+ struct profiler_bw_cntrs_req __user *data)
+{
+ compat_uint_t total;
+ compat_uint_t cpu;
+ compat_uint_t gpu;
+ compat_uint_t cmd;
+ int err;
+
+ err = get_user(total, &data->total);
+ err |= put_user(total, &data32->total);
+ err |= get_user(gpu, &data->gpu);
+ err |= put_user(gpu, &data32->gpu);
+ err |= get_user(cpu, &data->cpu);
+ err |= put_user(cpu, &data32->cpu);
+ err |= get_user(cmd, &data->cmd);
+ err |= put_user(cmd, &data32->cmd);
+ return err;
+}
+
+static unsigned int convert_cmd(unsigned int cmd)
+{
+ switch (cmd) {
+ case COMPAT_PROFILER_IOCTL_GET_BW_INFO:
+ return PROFILER_IOCTL_GET_BW_INFO;
+
+ default:
+ return cmd;
+ }
+}
+
+
+long profiler_ioctl(struct file *file, unsigned cmd, unsigned long arg)
+{
+ int ret = 0;
+ struct profiler_dev_handle *data = file->private_data;
+ void __user *argp = (void __user *) arg;
+
+ if (!data) {
+ pr_err("Invalid/uninitialized device handle\n");
+ return -EINVAL;
+ }
+
+ if (data->abort) {
+ pr_err("Aborting profiler driver\n");
+ return -ENODEV;
+ }
+
+ switch (cmd) {
+ case PROFILER_IOCTL_GET_BW_INFO:
+ atomic_inc(&data->ioctl_count);
+ ret = profiler_get_bw_info(argp);
+ if (ret)
+ pr_err("failed get system bandwidth info: %d\n", ret);
+ atomic_dec(&data->ioctl_count);
+ break;
+ default:
+ pr_err("Invalid IOCTL: 0x%x\n", cmd);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+long compat_profiler_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ long ret;
+
+ switch (cmd) {
+ case COMPAT_PROFILER_IOCTL_GET_BW_INFO:{
+ struct compat_profiler_bw_cntrs_req __user *data32;
+ struct profiler_bw_cntrs_req __user *data;
+ int err;
+
+ data32 = compat_ptr(arg);
+ data = compat_alloc_user_space(sizeof(*data));
+ if (data == NULL)
+ return -EFAULT;
+ err = compat_get_profiler_bw_info(data32, data);
+ if (err)
+ return err;
+ ret = profiler_ioctl(file, convert_cmd(cmd),
+ (unsigned long)data);
+ err = compat_put_profiler_bw_info(data32, data);
+ return ret ? ret : err;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+
+static int profiler_release(struct inode *inode, struct file *file)
+{
+ pr_info("profiler release\n");
+ return 0;
+}
+
+static const struct file_operations profiler_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = profiler_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = compat_profiler_ioctl,
+#endif
+ .open = profiler_open,
+ .release = profiler_release
+};
+
+static int profiler_init(void)
+{
+ int rc;
+ struct device *class_dev;
+
+ rc = alloc_chrdev_region(&profiler_device_no, 0, 1, PROFILER_DEV);
+ if (rc < 0) {
+ pr_err("alloc_chrdev_region failed %d\n", rc);
+ return rc;
+ }
+
+ driver_class = class_create(THIS_MODULE, PROFILER_DEV);
+ if (IS_ERR(driver_class)) {
+ rc = -ENOMEM;
+ pr_err("class_create failed %d\n", rc);
+ goto exit_unreg_chrdev_region;
+ }
+
+ class_dev = device_create(driver_class, NULL, profiler_device_no, NULL,
+ PROFILER_DEV);
+ if (IS_ERR(class_dev)) {
+ pr_err("class_device_create failed %d\n", rc);
+ rc = -ENOMEM;
+ goto exit_destroy_class;
+ }
+
+ cdev_init(&profiler.cdev, &profiler_fops);
+ profiler.cdev.owner = THIS_MODULE;
+
+ rc = cdev_add(&profiler.cdev, MKDEV(MAJOR(profiler_device_no), 0), 1);
+ if (rc < 0) {
+ pr_err("%s: cdev_add failed %d\n", __func__, rc);
+ goto exit_destroy_device;
+ }
+
+ profiler.pdev = class_dev;
+ return 0;
+
+exit_destroy_device:
+ device_destroy(driver_class, profiler_device_no);
+exit_destroy_class:
+ class_destroy(driver_class);
+exit_unreg_chrdev_region:
+ unregister_chrdev_region(profiler_device_no, 1);
+ return rc;
+}
+
+static void profiler_exit(void)
+{
+ pr_info("Exiting from profiler\n");
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. trustzone Communicator");
+
+module_init(profiler_init);
+module_exit(profiler_exit);