kernel_samsung_a34x-permissive/drivers/misc/tzdev/4.2.1/debug/profiler.c
2024-04-28 15:49:01 +02:00

702 lines
20 KiB
C
Executable file

/*
* Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/completion.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/rtc.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include "lib/circ_buf.h"
#include "tzdev_internal.h"
#include "core/cdev.h"
#include "core/cred.h"
#include "core/iwio.h"
#include "core/log.h"
#include "core/notifier.h"
#include "core/subsystem.h"
#include "debug/profiler.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eugene Mandrenko <i.mandrenko@samsung.com>");
MODULE_DESCRIPTION("Trustzone profiler driver");
#define TZPROFILER_IOC_MAGIC 'p'
#define TZPROFILER_INCREASE_POOL _IOW(TZPROFILER_IOC_MAGIC, 0, uint32_t)
#define TZPROFILER_START _IO(TZPROFILER_IOC_MAGIC, 1)
#define TZPROFILER_STOP _IO(TZPROFILER_IOC_MAGIC, 2)
#define TZPROFILER_SET_DEPTH _IOW(TZPROFILER_IOC_MAGIC, 3, uint32_t)
#define TZPROFILER_SET_UUID _IOW(TZPROFILER_IOC_MAGIC, 4, struct tz_uuid)
#define TZPROFILER_SET_START_ADDR _IOW(TZPROFILER_IOC_MAGIC, 5, uint64_t)
#define TZPROFILER_SET_STOP_ADDR _IOW(TZPROFILER_IOC_MAGIC, 6, uint64_t)
#define TZPROFILER_SET_STEPS_NUMBER _IOW(TZPROFILER_IOC_MAGIC, 7, uint32_t)
#define TZDEV_PROFILER_BUF_SIZE (CONFIG_TZPROFILER_BUF_PG_CNT * PAGE_SIZE -\
sizeof(struct circ_buf))
enum {
TZPROFILER_LIST1_NEED_CLEAN = 0,
TZPROFILER_LIST2_NEED_CLEAN
};
enum {
TZDEV_PROFILER_CMD_START,
TZDEV_PROFILER_CMD_STOP,
TZDEV_PROFILER_CMD_SET_DEPTH,
TZDEV_PROFILER_CMD_SET_UUID,
TZDEV_PROFILER_CMD_SET_START_ADDR,
TZDEV_PROFILER_CMD_SET_STOP_ADDR,
TZDEV_PROFILER_CMD_SET_STEPS_NUMBER,
TZDEV_PROFILER_CMD_COUNT,
};
struct profiler_buf_entry {
struct list_head list;
struct circ_buf *tzio_buf;
};
/* Specifies if the buffer pool is initialized */
static DECLARE_COMPLETION(tzprofiler_pool_completion);
/* There are two lists. One list contains buffers that wait for reading data from them
* (data from one of them is currently read). Another list contains buffers that have been read.
* After reading of all data from a buffer from the first list the buffer moves to the second
* list, and the next buffer in the first list begin to be read. When all buffers are moved from
* the first list to the second one, the lists swap roles, and after that the second list contains
* buffer that have to be read, and after reading they move to the first list. This process is
* cyclic. current_full_pool variable specifies list with buffers that have currently to be read. */
LIST_HEAD(profiler_buf_list1);
LIST_HEAD(profiler_buf_list2);
static int current_full_pool = TZPROFILER_LIST1_NEED_CLEAN;
/* 1 if Profiler is initialized, 0 otherwise */
static atomic_t tzprofiler_init_done = ATOMIC_INIT(0);
/* Number of threads in SWd */
static atomic_t sk_is_active_count = ATOMIC_INIT(0);
/* Check if the data is currently being read */
static atomic_t tzprofiler_data_is_being_written = ATOMIC_INIT(0);
/* Specifies how many additional idle iterations remain to do after exit from SWd.
* The value of the variable is set in post-SMC and decreases in reading loop at each
* iteration when its value is nonzero and no data was read. We need it in order to read
* remaining data that might be written from SWd in buffers that at the time of exit from
* SWd are located in the list where the buffers move to after reading. */
static atomic_t tzprofiler_last_passing = ATOMIC_INIT(0);
/* Waitqueue for waiting until SWd becomes active */
static DECLARE_WAIT_QUEUE_HEAD(sk_wait);
/* Waitqueue for waiting until data reading finish */
static DECLARE_WAIT_QUEUE_HEAD(tzprofiler_data_writing);
/**
* Initialize buffer pool in which profiling data will being written in SWd
* @param[in] bufs_cnt number of buffers
* @param[in] buf_pg_cnt number of pages in each buffer
* @return 0 if succeess, -ENOMEM if memory allocation failed, -ENXIO if IW
* channel allocation failed
*/
static int tzprofiler_init_buf_pool(unsigned int bufs_cnt, unsigned int buf_pg_cnt)
{
struct profiler_buf_entry *profiler_buf;
unsigned int i;
for (i = 0; i < bufs_cnt; ++i) {
profiler_buf = kmalloc(sizeof(struct profiler_buf_entry), GFP_KERNEL);
if (profiler_buf == NULL)
return -ENOMEM;
profiler_buf->tzio_buf = tz_iwio_alloc_iw_channel(
TZ_IWIO_CONNECT_PROFILER, buf_pg_cnt, NULL, NULL, NULL);
if (IS_ERR(profiler_buf->tzio_buf)) {
kfree(profiler_buf);
return -ENXIO;
}
profiler_buf->tzio_buf->write_count = profiler_buf->tzio_buf->read_count = 0;
list_add(&profiler_buf->list, &profiler_buf_list1);
}
complete_all(&tzprofiler_pool_completion);
return 0;
}
/**
* Set depth (max number of nested functions to be profiled) in SWd
* @param[in] depth
* @return result of SMC
*/
static int tzprofiler_set_depth(unsigned int depth)
{
int ret;
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_SET_DEPTH, depth);
if (ret == 0) {
log_debug(tzdev_profiler, "Set depth successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: depth setup error. ret = %d\n", ret);
}
return ret;
}
/**
* Start profiler in SWd
* @return result of SMC
*/
static int tzprofiler_start(void)
{
int ret;
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_START, 0);
if (ret == 0) {
log_debug(tzdev_profiler, "Profiler has started successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: Profiler has not started. ret = %d\n", ret);
}
return ret;
}
/**
* Stop profiler in SWd
* @return result of SMC
*/
static int tzprofiler_stop(void)
{
int ret;
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_STOP, 0);
if (ret == 0) {
log_debug(tzdev_profiler, "Profiler has stopped successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: Profiler has not stopped. ret = %d\n", ret);
}
return ret;
}
/**
* Set UUID of the app to be profiled
* @param[in] uuid
* @return result of SMC
*/
static int tzprofiler_set_uuid(struct tz_uuid *uuid)
{
int ret = 0;
struct tz_iwio_aux_channel *ch;
ch = tz_iwio_get_aux_channel();
memcpy(ch->buffer, uuid, sizeof(struct tz_uuid));
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_SET_UUID, 0);
tz_iwio_put_aux_channel();
if (ret == 0) {
log_debug(tzdev_profiler, "Set uuid successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: uuid setup error. ret = %d\n", ret);
}
return ret;
}
/**
* Set start address (the address, after which profiling will be performed)
* @param[in] addr
* @return result of SMC
*/
static int tzprofiler_set_start_addr(uint64_t *addr)
{
int ret;
struct tz_iwio_aux_channel *ch;
ch = tz_iwio_get_aux_channel();
memcpy(ch->buffer, addr, sizeof(uint64_t));
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_SET_START_ADDR, 0);
tz_iwio_put_aux_channel();
if (ret == 0) {
log_debug(tzdev_profiler, "Set start addr successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: start addr setup error. ret = %d\n", ret);
}
return ret;
}
/**
* Set stop address (the address, before which profiling will be performed)
* @param[in] addr
* @return result of SMC
*/
static int tzprofiler_set_stop_addr(uint64_t *addr)
{
int ret;
struct tz_iwio_aux_channel *ch;
ch = tz_iwio_get_aux_channel();
memcpy(ch->buffer, addr, sizeof(uint64_t));
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_SET_STOP_ADDR, 0);
tz_iwio_put_aux_channel();
if (ret == 0) {
log_debug(tzdev_profiler, "Set stop addr successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: stop addr setup error. ret = %d\n", ret);
}
return ret;
}
/**
* Set steps number (the number of function calls under profiler)
* @param[in] steps
* @return result of SMC
*/
static int tzprofiler_set_steps_number(uint32_t steps)
{
int ret;
struct tz_iwio_aux_channel *ch;
ch = tz_iwio_get_aux_channel();
memcpy(ch->buffer, &steps, sizeof(uint32_t));
ret = tzdev_smc_profiler_control(TZDEV_PROFILER_CMD_SET_STEPS_NUMBER, 0);
tz_iwio_put_aux_channel();
if (ret == 0) {
log_debug(tzdev_profiler, "Set number of steps successfully\n");
} else if (ret == -ENOSYS) {
ret = 0;
log_debug(tzdev_profiler, "SWd is built without profiler\n");
} else {
log_error(tzdev_profiler, "failed: number of steps setup error. ret = %d\n", ret);
}
return ret;
}
/**
* Add buffers to the buffer pool
* @param[in] number number of buffers to be added
* @return 0 if succeess, -ENOMEM if memory allocation failed, -ENXIO if IW
* channel allocation failed
*/
static int tzprofiler_add_buffers(unsigned int number)
{
int ret;
ret = tzprofiler_stop();
if (ret) {
log_error(tzdev_profiler, "tzprofiler_stop failed: %d\n", ret);
return ret;
}
ret = tzprofiler_init_buf_pool(number, CONFIG_TZPROFILER_BUF_PG_CNT);
if (ret) {
log_error(tzdev_profiler, "tzprofiler_init_buf_pool failed: %d\n", ret);
return ret;
}
ret = tzprofiler_start();
if (ret)
log_error(tzdev_profiler, "tzprofiler_start failed: %d\n", ret);
return ret;
}
/**
* Read data from s_buf (kernel space memory) to d_buf (user space memory)
* @param[in] s_buf the buffer which the data is read from
* @param[in] quantity number of bytes to read
* @param[out] d_buf the buffer which the data is written to
* @param[inout] saved_count pointer to a variable that contains beginning position of writing
* to d_buf. The variable is increased by the number of currently written bytes.
* @param[in] count full size of s_buf
* @return number of currently written bytes
*/
static ssize_t tzprofiler_write_buf(unsigned char *s_buf, ssize_t quantity,
unsigned char __user *d_buf, ssize_t *saved_count, size_t count)
{
ssize_t real_saved;
if ((*saved_count + quantity) <= count)
real_saved = quantity;
else
real_saved = count - *saved_count;
if (copy_to_user(&d_buf[*saved_count], s_buf, real_saved))
log_error(tzdev_profiler, "can't copy to user\n");
*saved_count += real_saved;
return real_saved;
}
/**
* Read count bytes from the buffer pool to buf
* @param[out] buf output buffer of the driver's reading operation
* @param[in] count max number of bytes to read
* @param[inout] head list of buffers from which data is currently read
* @param[inout] cleaned_head list of buffers that have been read
* @return number of actually read symbols
*/
static ssize_t __read(char __user *buf, size_t count, struct list_head *head,
struct list_head *cleaned_head)
{
struct circ_buf *tzio_buf;
struct profiler_buf_entry *profiler_buf, *tmp;
ssize_t bytes, quantity, write_count, saved_count = 0;
/* Iterate through each buffer of list "head" until "head" become empty or
* some data will be read from a buffer */
list_for_each_entry_safe(profiler_buf, tmp, head, list) {
tzio_buf = profiler_buf->tzio_buf;
if (tzio_buf->read_count == tzio_buf->write_count) {
list_del(&profiler_buf->list);
list_add(&profiler_buf->list, cleaned_head);
continue;
}
write_count = tzio_buf->write_count;
if (write_count < tzio_buf->read_count) {
quantity = TZDEV_PROFILER_BUF_SIZE - tzio_buf->read_count;
bytes = tzprofiler_write_buf(tzio_buf->buffer + tzio_buf->read_count,
quantity, buf, &saved_count, count);
if (bytes < quantity) {
tzio_buf->read_count += bytes;
atomic_set(&tzprofiler_data_is_being_written, 1);
return saved_count;
}
tzio_buf->read_count = 0;
}
quantity = write_count - tzio_buf->read_count;
bytes = tzprofiler_write_buf(tzio_buf->buffer + tzio_buf->read_count, quantity,
buf, &saved_count, count);
if (bytes < quantity) {
tzio_buf->read_count += bytes;
atomic_set(&tzprofiler_data_is_being_written, 1);
return saved_count;
}
tzio_buf->read_count += quantity;
list_del(&profiler_buf->list);
list_add(&profiler_buf->list, cleaned_head);
}
return saved_count;
}
/**
* Reading operation handler of the char driver. It reads the data from the buffer pool
*/
static ssize_t tzprofiler_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct list_head *current_head, *cleaned_head;
size_t saved_count;
int ret;
atomic_set(&tzprofiler_data_is_being_written, 0);
/* Hung in the loop until some data is read. It is implemented since reading operation handler
* is required to read some data, otherwise reading of zero-length data might be considered
* as the end of all data. */
while (1) {
if ((atomic_read(&sk_is_active_count) == 0) && (atomic_read(&tzprofiler_last_passing) == 0)) {
/* Wait until a thread switches to SWd, a signal is received or 5 seconds expire.
* It is implemented to avoid continuous looping while there are no threads in SWd. */
ret = wait_event_interruptible_timeout(sk_wait,
atomic_read(&sk_is_active_count) != 0, msecs_to_jiffies(5000));
if (ret < 0)
return (ssize_t)-EINTR;
}
/* Wait until the buffer pool is initialized */
ret = wait_for_completion_interruptible(&tzprofiler_pool_completion);
if (unlikely(ret < 0))
return (ssize_t)-EINTR;
if (current_full_pool == TZPROFILER_LIST1_NEED_CLEAN) {
current_head = &profiler_buf_list1;
cleaned_head = &profiler_buf_list2;
} else {
current_head = &profiler_buf_list2;
cleaned_head = &profiler_buf_list1;
}
/* Read data from the buffer pool to buf */
saved_count = __read(buf, count, current_head, cleaned_head);
/* Check if some data has been read */
if (saved_count) {
atomic_set(&tzprofiler_data_is_being_written, 1);
return saved_count;
}
/* The following code (to the end of the loop) is executed only if no data has been read */
wake_up(&tzprofiler_data_writing);
if (atomic_read(&tzprofiler_last_passing) > 0)
atomic_dec(&tzprofiler_last_passing);
/* Swap lists' roles */
if (current_full_pool == TZPROFILER_LIST1_NEED_CLEAN)
current_full_pool = TZPROFILER_LIST2_NEED_CLEAN;
else
current_full_pool = TZPROFILER_LIST1_NEED_CLEAN;
}
return 0;
}
/**
* Opening operation handler of the char driver.
*/
static int tzprofiler_open(struct inode *inode, struct file *filp)
{
return 0;
}
/**
* unlocked_ioctl operation handler of the char driver.
*/
static long tzprofiler_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret;
uint64_t addr;
struct tz_uuid uuid;
switch (cmd) {
case TZPROFILER_INCREASE_POOL:
ret = tzprofiler_add_buffers(arg);
break;
case TZPROFILER_SET_DEPTH:
ret = tzprofiler_set_depth(arg);
break;
case TZPROFILER_START:
ret = tzprofiler_start();
break;
case TZPROFILER_STOP:
ret = tzprofiler_stop();
break;
case TZPROFILER_SET_UUID:
if (copy_from_user(&uuid, (void *)arg, sizeof(struct tz_uuid)))
return -EFAULT;
ret = tzprofiler_set_uuid(&uuid);
break;
case TZPROFILER_SET_START_ADDR:
if (copy_from_user(&addr, (void *)arg, sizeof(uint64_t)))
return -EFAULT;
ret = tzprofiler_set_start_addr(&addr);
break;
case TZPROFILER_SET_STOP_ADDR:
if (copy_from_user(&addr, (void *)arg, sizeof(uint64_t)))
return -EFAULT;
ret = tzprofiler_set_stop_addr(&addr);
break;
case TZPROFILER_SET_STEPS_NUMBER:
ret = tzprofiler_set_steps_number(arg);
break;
default:
ret = -ENOTTY;
log_error(tzdev_profiler, "Unexpected command %u\n", cmd);
break;
}
return ret;
}
/**
* Releasing operation handler of the char driver.
*/
static int tzprofiler_release(struct inode *inode, struct file *filp)
{
atomic_set(&tzprofiler_data_is_being_written, 0);
wake_up(&tzprofiler_data_writing);
return 0;
}
static const struct file_operations tzprofiler_fops = {
.owner = THIS_MODULE,
.read = tzprofiler_read,
.open = tzprofiler_open,
.unlocked_ioctl = tzprofiler_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = tzprofiler_unlocked_ioctl,
#endif /* CONFIG_COMPAT */
.release = tzprofiler_release,
};
static struct tz_cdev tzprofiler_cdev = {
.name = "tzprofiler",
.fops = &tzprofiler_fops,
.owner = THIS_MODULE,
};
/**
* The function will be invoked before each SMC
*/
void tzprofiler_pre_smc_call_direct(void)
{
atomic_inc(&sk_is_active_count);
atomic_set(&tzprofiler_last_passing, 2);
wake_up(&sk_wait);
}
/**
* Wait until all data will be read from the buffer pool
*/
static void tzprofiler_wait_for_bufs(void)
{
int ret;
if (atomic_read(&tzprofiler_data_is_being_written) == 0)
return;
if (!in_atomic()) {
ret = wait_event_interruptible(tzprofiler_data_writing,
(atomic_read(&tzprofiler_data_is_being_written) == 0) ||
(atomic_read(&tzprofiler_last_passing) == 0));
if (ret < 0) {
log_debug(tzdev_profiler, "waiting for buffers interrupted!\n");
atomic_set(&tzprofiler_data_is_being_written, 0);
}
}
}
/**
* The function will be invoked after each SMC
*/
void tzprofiler_post_smc_call_direct(void)
{
tzprofiler_wait_for_bufs();
atomic_set(&tzprofiler_last_passing, 2);
wake_up(&sk_wait);
atomic_dec(&sk_is_active_count);
}
/**
* Profiler initialization
*/
static int tzprofiler_init_call(struct notifier_block *cb, unsigned long code, void *unused)
{
int rc;
(void)cb;
(void)code;
(void)unused;
rc = tzprofiler_init_buf_pool(CONFIG_TZPROFILER_BUFS_CNT, CONFIG_TZPROFILER_BUF_PG_CNT);
if (rc)
return NOTIFY_DONE;
rc = tzprofiler_start();
if (rc)
return NOTIFY_DONE;
rc = tz_cdev_register(&tzprofiler_cdev);
if (rc)
goto profiler_device_reg_failed;
atomic_set(&tzprofiler_init_done, 1);
log_info(tzdev_profiler, "Profiler initialization done.\n");
return NOTIFY_DONE;
profiler_device_reg_failed:
tzprofiler_stop();
log_error(tzdev_profiler, "Profiler initialization failed!\n");
return NOTIFY_DONE;
}
/**
* Profiler finalization
*/
static int tzprofiler_fini_call(struct notifier_block *cb, unsigned long code, void *unused)
{
(void)cb;
(void)code;
(void)unused;
/* Check if the profiler is initialized */
if (!atomic_cmpxchg(&tzprofiler_init_done, 1, 0)) {
log_info(tzdev_profiler, "Profile not initialized.\n");
return NOTIFY_DONE;
}
tz_cdev_unregister(&tzprofiler_cdev);
tzprofiler_stop();
log_info(tzdev_profiler, "Profiler finalization done.\n");
return NOTIFY_DONE;
}
static struct notifier_block tzprofiler_init_notifier = {
.notifier_call = tzprofiler_init_call,
};
static struct notifier_block tzprofiler_fini_notifier = {
.notifier_call = tzprofiler_fini_call,
};
/**
* Profiler's driver initialization. It registers profiler's initialization
* and finalization function in corresponding global notifiers.
*/
int tzprofiler_init(void)
{
int rc;
rc = tzdev_blocking_notifier_register(TZDEV_INIT_NOTIFIER, &tzprofiler_init_notifier);
if (rc)
return rc;
rc = tzdev_blocking_notifier_register(TZDEV_FINI_NOTIFIER, &tzprofiler_fini_notifier);
if (rc) {
tzdev_blocking_notifier_unregister(TZDEV_INIT_NOTIFIER, &tzprofiler_init_notifier);
return rc;
}
log_info(tzdev_profiler, "Profiler callbacks registration done\n");
return 0;
}
tzdev_early_initcall(tzprofiler_init);