diff options
Diffstat (limited to 'drivers/misc/profiler.c')
-rw-r--r-- | drivers/misc/profiler.c | 461 |
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); |