| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org> |
| * |
| * Runtime reactor interface. |
| * |
| * A runtime monitor can cause a reaction to the detection of an |
| * exception on the model's execution. By default, the monitors have |
| * tracing reactions, printing the monitor output via tracepoints. |
| * But other reactions can be added (on-demand) via this interface. |
| * |
| * == Registering reactors == |
| * |
| * The struct rv_reactor defines a callback function to be executed |
| * in case of a model exception happens. The callback function |
| * receives a message to be (optionally) printed before executing |
| * the reaction. |
| * |
| * A RV reactor is registered via: |
| * int rv_register_reactor(struct rv_reactor *reactor) |
| * And unregistered via: |
| * int rv_unregister_reactor(struct rv_reactor *reactor) |
| * |
| * These functions are exported to modules, enabling reactors to be |
| * dynamically loaded. |
| * |
| * == User interface == |
| * |
| * The user interface resembles the kernel tracing interface and |
| * presents these files: |
| * |
| * "available_reactors" |
| * - List the available reactors, one per line. |
| * |
| * For example: |
| * # cat available_reactors |
| * nop |
| * panic |
| * printk |
| * |
| * "reacting_on" |
| * - It is an on/off general switch for reactors, disabling |
| * all reactions. |
| * |
| * "monitors/MONITOR/reactors" |
| * - List available reactors, with the select reaction for the given |
| * MONITOR inside []. The default one is the nop (no operation) |
| * reactor. |
| * - Writing the name of an reactor enables it to the given |
| * MONITOR. |
| * |
| * For example: |
| * # cat monitors/wip/reactors |
| * [nop] |
| * panic |
| * printk |
| * # echo panic > monitors/wip/reactors |
| * # cat monitors/wip/reactors |
| * nop |
| * [panic] |
| * printk |
| */ |
| |
| #include <linux/slab.h> |
| |
| #include "rv.h" |
| |
| /* |
| * Interface for the reactor register. |
| */ |
| static LIST_HEAD(rv_reactors_list); |
| |
| static struct rv_reactor_def *get_reactor_rdef_by_name(char *name) |
| { |
| struct rv_reactor_def *r; |
| |
| list_for_each_entry(r, &rv_reactors_list, list) { |
| if (strcmp(name, r->reactor->name) == 0) |
| return r; |
| } |
| return NULL; |
| } |
| |
| /* |
| * Available reactors seq functions. |
| */ |
| static int reactors_show(struct seq_file *m, void *p) |
| { |
| struct rv_reactor_def *rea_def = p; |
| |
| seq_printf(m, "%s\n", rea_def->reactor->name); |
| return 0; |
| } |
| |
| static void reactors_stop(struct seq_file *m, void *p) |
| { |
| mutex_unlock(&rv_interface_lock); |
| } |
| |
| static void *reactors_start(struct seq_file *m, loff_t *pos) |
| { |
| mutex_lock(&rv_interface_lock); |
| return seq_list_start(&rv_reactors_list, *pos); |
| } |
| |
| static void *reactors_next(struct seq_file *m, void *p, loff_t *pos) |
| { |
| return seq_list_next(p, &rv_reactors_list, pos); |
| } |
| |
| /* |
| * available_reactors seq definition. |
| */ |
| static const struct seq_operations available_reactors_seq_ops = { |
| .start = reactors_start, |
| .next = reactors_next, |
| .stop = reactors_stop, |
| .show = reactors_show |
| }; |
| |
| /* |
| * available_reactors interface. |
| */ |
| static int available_reactors_open(struct inode *inode, struct file *file) |
| { |
| return seq_open(file, &available_reactors_seq_ops); |
| }; |
| |
| static const struct file_operations available_reactors_ops = { |
| .open = available_reactors_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release |
| }; |
| |
| /* |
| * Monitor's reactor file. |
| */ |
| static int monitor_reactor_show(struct seq_file *m, void *p) |
| { |
| struct rv_monitor_def *mdef = m->private; |
| struct rv_reactor_def *rdef = p; |
| |
| if (mdef->rdef == rdef) |
| seq_printf(m, "[%s]\n", rdef->reactor->name); |
| else |
| seq_printf(m, "%s\n", rdef->reactor->name); |
| return 0; |
| } |
| |
| /* |
| * available_reactors seq definition. |
| */ |
| static const struct seq_operations monitor_reactors_seq_ops = { |
| .start = reactors_start, |
| .next = reactors_next, |
| .stop = reactors_stop, |
| .show = monitor_reactor_show |
| }; |
| |
| static void monitor_swap_reactors(struct rv_monitor_def *mdef, struct rv_reactor_def *rdef, |
| bool reacting) |
| { |
| bool monitor_enabled; |
| |
| /* nothing to do */ |
| if (mdef->rdef == rdef) |
| return; |
| |
| monitor_enabled = mdef->monitor->enabled; |
| if (monitor_enabled) |
| rv_disable_monitor(mdef); |
| |
| /* swap reactor's usage */ |
| mdef->rdef->counter--; |
| rdef->counter++; |
| |
| mdef->rdef = rdef; |
| mdef->reacting = reacting; |
| mdef->monitor->react = rdef->reactor->react; |
| |
| if (monitor_enabled) |
| rv_enable_monitor(mdef); |
| } |
| |
| static ssize_t |
| monitor_reactors_write(struct file *file, const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| char buff[MAX_RV_REACTOR_NAME_SIZE + 2]; |
| struct rv_monitor_def *mdef; |
| struct rv_reactor_def *rdef; |
| struct seq_file *seq_f; |
| int retval = -EINVAL; |
| bool enable; |
| char *ptr; |
| int len; |
| |
| if (count < 1 || count > MAX_RV_REACTOR_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); |
| |
| len = strlen(ptr); |
| if (!len) |
| return count; |
| |
| /* |
| * See monitor_reactors_open() |
| */ |
| seq_f = file->private_data; |
| mdef = seq_f->private; |
| |
| mutex_lock(&rv_interface_lock); |
| |
| retval = -EINVAL; |
| |
| list_for_each_entry(rdef, &rv_reactors_list, list) { |
| if (strcmp(ptr, rdef->reactor->name) != 0) |
| continue; |
| |
| if (rdef == get_reactor_rdef_by_name("nop")) |
| enable = false; |
| else |
| enable = true; |
| |
| monitor_swap_reactors(mdef, rdef, enable); |
| |
| retval = count; |
| break; |
| } |
| |
| mutex_unlock(&rv_interface_lock); |
| |
| return retval; |
| } |
| |
| /* |
| * available_reactors interface. |
| */ |
| static int monitor_reactors_open(struct inode *inode, struct file *file) |
| { |
| struct rv_monitor_def *mdef = inode->i_private; |
| struct seq_file *seq_f; |
| int ret; |
| |
| ret = seq_open(file, &monitor_reactors_seq_ops); |
| if (ret < 0) |
| return ret; |
| |
| /* |
| * seq_open stores the seq_file on the file->private data. |
| */ |
| seq_f = file->private_data; |
| |
| /* |
| * Copy the create file "private" data to the seq_file private data. |
| */ |
| seq_f->private = mdef; |
| |
| return 0; |
| }; |
| |
| static const struct file_operations monitor_reactors_ops = { |
| .open = monitor_reactors_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| .write = monitor_reactors_write |
| }; |
| |
| static int __rv_register_reactor(struct rv_reactor *reactor) |
| { |
| struct rv_reactor_def *r; |
| |
| list_for_each_entry(r, &rv_reactors_list, list) { |
| if (strcmp(reactor->name, r->reactor->name) == 0) { |
| pr_info("Reactor %s is already registered\n", reactor->name); |
| return -EINVAL; |
| } |
| } |
| |
| r = kzalloc(sizeof(struct rv_reactor_def), GFP_KERNEL); |
| if (!r) |
| return -ENOMEM; |
| |
| r->reactor = reactor; |
| r->counter = 0; |
| |
| list_add_tail(&r->list, &rv_reactors_list); |
| |
| return 0; |
| } |
| |
| /** |
| * rv_register_reactor - register a rv reactor. |
| * @reactor: The rv_reactor to be registered. |
| * |
| * Returns 0 if successful, error otherwise. |
| */ |
| int rv_register_reactor(struct rv_reactor *reactor) |
| { |
| int retval = 0; |
| |
| if (strlen(reactor->name) >= MAX_RV_REACTOR_NAME_SIZE) { |
| pr_info("Reactor %s has a name longer than %d\n", |
| reactor->name, MAX_RV_MONITOR_NAME_SIZE); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&rv_interface_lock); |
| retval = __rv_register_reactor(reactor); |
| mutex_unlock(&rv_interface_lock); |
| return retval; |
| } |
| |
| /** |
| * rv_unregister_reactor - unregister a rv reactor. |
| * @reactor: The rv_reactor to be unregistered. |
| * |
| * Returns 0 if successful, error otherwise. |
| */ |
| int rv_unregister_reactor(struct rv_reactor *reactor) |
| { |
| struct rv_reactor_def *ptr, *next; |
| int ret = 0; |
| |
| mutex_lock(&rv_interface_lock); |
| |
| list_for_each_entry_safe(ptr, next, &rv_reactors_list, list) { |
| if (strcmp(reactor->name, ptr->reactor->name) == 0) { |
| |
| if (!ptr->counter) { |
| list_del(&ptr->list); |
| } else { |
| printk(KERN_WARNING |
| "rv: the rv_reactor %s is in use by %d monitor(s)\n", |
| ptr->reactor->name, ptr->counter); |
| printk(KERN_WARNING "rv: the rv_reactor %s cannot be removed\n", |
| ptr->reactor->name); |
| ret = -EBUSY; |
| break; |
| } |
| } |
| } |
| |
| mutex_unlock(&rv_interface_lock); |
| return ret; |
| } |
| |
| /* |
| * reacting_on interface. |
| */ |
| static bool __read_mostly reacting_on; |
| |
| /** |
| * rv_reacting_on - checks if reacting is on |
| * |
| * Returns 1 if on, 0 otherwise. |
| */ |
| bool rv_reacting_on(void) |
| { |
| /* Ensures that concurrent monitors read consistent reacting_on */ |
| smp_rmb(); |
| return READ_ONCE(reacting_on); |
| } |
| |
| static ssize_t reacting_on_read_data(struct file *filp, |
| char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| char *buff; |
| |
| buff = rv_reacting_on() ? "1\n" : "0\n"; |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff)+1); |
| } |
| |
| static void turn_reacting_off(void) |
| { |
| WRITE_ONCE(reacting_on, false); |
| /* Ensures that concurrent monitors read consistent reacting_on */ |
| smp_wmb(); |
| } |
| |
| static void turn_reacting_on(void) |
| { |
| WRITE_ONCE(reacting_on, true); |
| /* Ensures that concurrent monitors read consistent reacting_on */ |
| smp_wmb(); |
| } |
| |
| static ssize_t reacting_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; |
| |
| mutex_lock(&rv_interface_lock); |
| |
| if (val) |
| turn_reacting_on(); |
| else |
| turn_reacting_off(); |
| |
| /* |
| * Wait for the execution of all events to finish |
| * before returning to user-space. |
| */ |
| tracepoint_synchronize_unregister(); |
| |
| mutex_unlock(&rv_interface_lock); |
| |
| return count; |
| } |
| |
| static const struct file_operations reacting_on_fops = { |
| .open = simple_open, |
| .llseek = no_llseek, |
| .write = reacting_on_write_data, |
| .read = reacting_on_read_data, |
| }; |
| |
| /** |
| * reactor_populate_monitor - creates per monitor reactors file |
| * @mdef: monitor's definition. |
| * |
| * Returns 0 if successful, error otherwise. |
| */ |
| int reactor_populate_monitor(struct rv_monitor_def *mdef) |
| { |
| struct dentry *tmp; |
| |
| tmp = rv_create_file("reactors", RV_MODE_WRITE, mdef->root_d, mdef, &monitor_reactors_ops); |
| if (!tmp) |
| return -ENOMEM; |
| |
| /* |
| * Configure as the rv_nop reactor. |
| */ |
| mdef->rdef = get_reactor_rdef_by_name("nop"); |
| mdef->rdef->counter++; |
| mdef->reacting = false; |
| |
| return 0; |
| } |
| |
| /** |
| * reactor_cleanup_monitor - cleanup a monitor reference |
| * @mdef: monitor's definition. |
| */ |
| void reactor_cleanup_monitor(struct rv_monitor_def *mdef) |
| { |
| lockdep_assert_held(&rv_interface_lock); |
| mdef->rdef->counter--; |
| WARN_ON_ONCE(mdef->rdef->counter < 0); |
| } |
| |
| /* |
| * Nop reactor register |
| */ |
| static void rv_nop_reaction(char *msg) |
| { |
| } |
| |
| static struct rv_reactor rv_nop = { |
| .name = "nop", |
| .description = "no-operation reactor: do nothing.", |
| .react = rv_nop_reaction |
| }; |
| |
| int init_rv_reactors(struct dentry *root_dir) |
| { |
| struct dentry *available, *reacting; |
| int retval; |
| |
| available = rv_create_file("available_reactors", RV_MODE_READ, root_dir, NULL, |
| &available_reactors_ops); |
| if (!available) |
| goto out_err; |
| |
| reacting = rv_create_file("reacting_on", RV_MODE_WRITE, root_dir, NULL, &reacting_on_fops); |
| if (!reacting) |
| goto rm_available; |
| |
| retval = __rv_register_reactor(&rv_nop); |
| if (retval) |
| goto rm_reacting; |
| |
| turn_reacting_on(); |
| |
| return 0; |
| |
| rm_reacting: |
| rv_remove(reacting); |
| rm_available: |
| rv_remove(available); |
| out_err: |
| return -ENOMEM; |
| } |