#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#ifdef CONFIG_RV_MON_EVENTS
#define CREATE_TRACE_POINTS
#include <rv_trace.h>
#endif
#include "rv.h"
DEFINE_MUTEX(rv_interface_lock);
static struct rv_interface rv_root;
struct dentry *get_monitors_root(void)
{
return rv_root.monitors_dir;
}
LIST_HEAD(rv_monitors_list);
static int task_monitor_count;
static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS];
int rv_get_task_monitor_slot(void)
{
int i;
lockdep_assert_held(&rv_interface_lock);
if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS)
return -EBUSY;
task_monitor_count++;
for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) {
if (task_monitor_slots[i] == false) {
task_monitor_slots[i] = true;
return i;
}
}
WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n");
return -EINVAL;
}
void rv_put_task_monitor_slot(int slot)
{
lockdep_assert_held(&rv_interface_lock);
if (slot < 0 || slot >= CONFIG_RV_PER_TASK_MONITORS) {
WARN_ONCE(1, "RV releasing an invalid slot!: %d\n", slot);
return;
}
WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n",
slot);
task_monitor_count--;
task_monitor_slots[slot] = false;
}
bool rv_is_nested_monitor(struct rv_monitor *mon)
{
return mon->parent != NULL;
}
bool rv_is_container_monitor(struct rv_monitor *mon)
{
struct rv_monitor *next;
if (list_is_last(&mon->list, &rv_monitors_list))
return false;
next = list_next_entry(mon, list);
return next->parent == mon || !mon->enable;
}
static ssize_t monitor_enable_read_data(struct file *filp, char __user *user_buf, size_t count,
loff_t *ppos)
{
struct rv_monitor *mon = filp->private_data;
const char *buff;
buff = mon->enabled ? "1\n" : "0\n";
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff)+1);
}
static int __rv_disable_monitor(struct rv_monitor *mon, bool sync)
{
lockdep_assert_held(&rv_interface_lock);
if (mon->enabled) {
mon->enabled = 0;
if (mon->disable)
mon->disable();
if (sync)
tracepoint_synchronize_unregister();
return 1;
}
return 0;
}
static void rv_disable_single(struct rv_monitor *mon)
{
__rv_disable_monitor(mon, true);
}
static int rv_enable_single(struct rv_monitor *mon)
{
int retval;
lockdep_assert_held(&rv_interface_lock);
if (mon->enabled)
return 0;
retval = mon->enable();
if (!retval)
mon->enabled = 1;
return retval;
}
static void rv_disable_container(struct rv_monitor *mon)
{
struct rv_monitor *p = mon;
int enabled = 0;
list_for_each_entry_continue(p, &rv_monitors_list, list) {
if (p->parent != mon)
break;
enabled += __rv_disable_monitor(p, false);
}
if (enabled)
tracepoint_synchronize_unregister();
mon->enabled = 0;
}
static int rv_enable_container(struct rv_monitor *mon)
{
struct rv_monitor *p = mon;
int retval = 0;
list_for_each_entry_continue(p, &rv_monitors_list, list) {
if (retval || p->parent != mon)
break;
retval = rv_enable_single(p);
}
if (retval)
rv_disable_container(mon);
else
mon->enabled = 1;
return retval;
}
int rv_disable_monitor(struct rv_monitor *mon)
{
if (rv_is_container_monitor(mon))
rv_disable_container(mon);
else
rv_disable_single(mon);
return 0;
}
int rv_enable_monitor(struct rv_monitor *mon)
{
int retval;
if (rv_is_container_monitor(mon))
retval = rv_enable_container(mon);
else
retval = rv_enable_single(mon);
return retval;
}
static ssize_t monitor_enable_write_data(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct rv_monitor *mon = filp->private_data;
int retval;
bool val;
retval = kstrtobool_from_user(user_buf, count, &val);
if (retval)
return retval;
guard(mutex)(&rv_interface_lock);
if (val)
retval = rv_enable_monitor(mon);
else
retval = rv_disable_monitor(mon);
return retval ? : count;
}
static const struct file_operations interface_enable_fops = {
.open = simple_open,
.write = monitor_enable_write_data,
.read = monitor_enable_read_data,
};
static ssize_t monitor_desc_read_data(struct file *filp, char __user *user_buf, size_t count,
loff_t *ppos)
{
struct rv_monitor *mon = filp->private_data;
char buff[256];
memset(buff, 0, sizeof(buff));
snprintf(buff, sizeof(buff), "%s\n", mon->description);
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff) + 1);
}
static const struct file_operations interface_desc_fops = {
.open = simple_open,
.read = monitor_desc_read_data,
};
static int create_monitor_dir(struct rv_monitor *mon, struct rv_monitor *parent)
{
struct dentry *root = parent ? parent->root_d : get_monitors_root();
struct dentry *dir __free(rv_remove) = rv_create_dir(mon->name, root);
struct dentry *tmp;
int retval;
if (!dir)
return -ENOMEM;
tmp = rv_create_file("enable", RV_MODE_WRITE, dir, mon, &interface_enable_fops);
if (!tmp)
return -ENOMEM;
tmp = rv_create_file("desc", RV_MODE_READ, dir, mon, &interface_desc_fops);
if (!tmp)
return -ENOMEM;
retval = reactor_populate_monitor(mon, dir);
if (retval)
return retval;
mon->root_d = no_free_ptr(dir);
return 0;
}
static int monitors_show(struct seq_file *m, void *p)
{
struct rv_monitor *mon = container_of(p, struct rv_monitor, list);
if (mon->parent)
seq_printf(m, "%s:%s\n", mon->parent->name, mon->name);
else
seq_printf(m, "%s\n", mon->name);
return 0;
}
static void monitors_stop(struct seq_file *m, void *p)
{
mutex_unlock(&rv_interface_lock);
}
static void *available_monitors_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&rv_interface_lock);
return seq_list_start(&rv_monitors_list, *pos);
}
static void *available_monitors_next(struct seq_file *m, void *p, loff_t *pos)
{
return seq_list_next(p, &rv_monitors_list, pos);
}
static void *enabled_monitors_next(struct seq_file *m, void *p, loff_t *pos)
{
struct rv_monitor *mon = container_of(p, struct rv_monitor, list);
(*pos)++;
list_for_each_entry_continue(mon, &rv_monitors_list, list) {
if (mon->enabled)
return &mon->list;
}
return NULL;
}
static void *enabled_monitors_start(struct seq_file *m, loff_t *pos)
{
struct list_head *head;
loff_t l;
mutex_lock(&rv_interface_lock);
if (list_empty(&rv_monitors_list))
return NULL;
head = &rv_monitors_list;
for (l = 0; l <= *pos; ) {
head = enabled_monitors_next(m, head, &l);
if (!head)
break;
}
return head;
}
static const struct seq_operations available_monitors_seq_ops = {
.start = available_monitors_start,
.next = available_monitors_next,
.stop = monitors_stop,
.show = monitors_show
};
static const struct seq_operations enabled_monitors_seq_ops = {
.start = enabled_monitors_start,
.next = enabled_monitors_next,
.stop = monitors_stop,
.show = monitors_show
};
static int available_monitors_open(struct inode *inode, struct file *file)
{
return seq_open(file, &available_monitors_seq_ops);
};
static const struct file_operations available_monitors_ops = {
.open = available_monitors_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static void disable_all_monitors(void)
{
struct rv_monitor *mon;
int enabled = 0;
guard(mutex)(&rv_interface_lock);
list_for_each_entry(mon, &rv_monitors_list, list)
enabled += __rv_disable_monitor(mon, false);
if (enabled) {
tracepoint_synchronize_unregister();
}
}
static int enabled_monitors_open(struct inode *inode, struct file *file)
{
if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC))
disable_all_monitors();
return seq_open(file, &enabled_monitors_seq_ops);
};
static ssize_t enabled_monitors_write(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buff[MAX_RV_MONITOR_NAME_SIZE + 2];
struct rv_monitor *mon;
int retval = -EINVAL;
bool enable = true;
char *ptr, *tmp;
int len;
if (count < 1 || count > MAX_RV_MONITOR_NAME_SIZE + 1)
return -EINVAL;
memset(buff, 0, sizeof(buff));
retval = simple_write_to_buffer(buff, sizeof(buff) - 1, ppos, user_buf, count);
if (retval < 0)
return -EFAULT;
ptr = strim(buff);
if (ptr[0] == '!') {
enable = false;
ptr++;
}
len = strlen(ptr);
if (!len)
return count;
guard(mutex)(&rv_interface_lock);
retval = -EINVAL;
tmp = strstr(ptr, ":");
if (tmp)
ptr = tmp+1;
list_for_each_entry(mon, &rv_monitors_list, list) {
if (strcmp(ptr, mon->name) != 0)
continue;
if (enable)
retval = rv_enable_monitor(mon);
else
retval = rv_disable_monitor(mon);
if (retval)
return retval;
return count;
}
return retval;
}
static const struct file_operations enabled_monitors_ops = {
.open = enabled_monitors_open,
.read = seq_read,
.write = enabled_monitors_write,
.llseek = seq_lseek,
.release = seq_release,
};
static bool __read_mostly monitoring_on;
bool rv_monitoring_on(void)
{
return READ_ONCE(monitoring_on);
}
static ssize_t monitoring_on_read_data(struct file *filp, char __user *user_buf,
size_t count, loff_t *ppos)
{
const char *buff;
buff = rv_monitoring_on() ? "1\n" : "0\n";
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff) + 1);
}
static void turn_monitoring_off(void)
{
WRITE_ONCE(monitoring_on, false);
}
static void reset_all_monitors(void)
{
struct rv_monitor *mon;
list_for_each_entry(mon, &rv_monitors_list, list) {
if (mon->enabled && mon->reset)
mon->reset();
}
}
static void turn_monitoring_on(void)
{
WRITE_ONCE(monitoring_on, true);
}
static void turn_monitoring_on_with_reset(void)
{
lockdep_assert_held(&rv_interface_lock);
if (rv_monitoring_on())
return;
reset_all_monitors();
turn_monitoring_on();
}
static ssize_t monitoring_on_write_data(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int retval;
bool val;
retval = kstrtobool_from_user(user_buf, count, &val);
if (retval)
return retval;
guard(mutex)(&rv_interface_lock);
if (val)
turn_monitoring_on_with_reset();
else
turn_monitoring_off();
tracepoint_synchronize_unregister();
return count;
}
static const struct file_operations monitoring_on_fops = {
.open = simple_open,
.write = monitoring_on_write_data,
.read = monitoring_on_read_data,
};
static void destroy_monitor_dir(struct rv_monitor *mon)
{
rv_remove(mon->root_d);
}
int rv_register_monitor(struct rv_monitor *monitor, struct rv_monitor *parent)
{
struct rv_monitor *r;
int retval = 0;
if (strlen(monitor->name) >= MAX_RV_MONITOR_NAME_SIZE) {
pr_info("Monitor %s has a name longer than %d\n", monitor->name,
MAX_RV_MONITOR_NAME_SIZE);
return -EINVAL;
}
guard(mutex)(&rv_interface_lock);
list_for_each_entry(r, &rv_monitors_list, list) {
if (strcmp(monitor->name, r->name) == 0) {
pr_info("Monitor %s is already registered\n", monitor->name);
return -EEXIST;
}
}
if (parent && rv_is_nested_monitor(parent)) {
pr_info("Parent monitor %s is already nested, cannot nest further\n",
parent->name);
return -EINVAL;
}
monitor->parent = parent;
retval = create_monitor_dir(monitor, parent);
if (retval)
return retval;
if (parent)
list_add(&monitor->list, &parent->list);
else
list_add_tail(&monitor->list, &rv_monitors_list);
return 0;
}
int rv_unregister_monitor(struct rv_monitor *monitor)
{
guard(mutex)(&rv_interface_lock);
rv_disable_monitor(monitor);
list_del(&monitor->list);
destroy_monitor_dir(monitor);
return 0;
}
int __init rv_init_interface(void)
{
struct dentry *tmp;
int retval;
struct dentry *root_dir __free(rv_remove) = rv_create_dir("rv", NULL);
if (!root_dir)
return 1;
rv_root.monitors_dir = rv_create_dir("monitors", root_dir);
if (!rv_root.monitors_dir)
return 1;
tmp = rv_create_file("available_monitors", RV_MODE_READ, root_dir, NULL,
&available_monitors_ops);
if (!tmp)
return 1;
tmp = rv_create_file("enabled_monitors", RV_MODE_WRITE, root_dir, NULL,
&enabled_monitors_ops);
if (!tmp)
return 1;
tmp = rv_create_file("monitoring_on", RV_MODE_WRITE, root_dir, NULL,
&monitoring_on_fops);
if (!tmp)
return 1;
retval = init_rv_reactors(root_dir);
if (retval)
return 1;
turn_monitoring_on();
rv_root.root_dir = no_free_ptr(root_dir);
return 0;
}