Something went wrong on our end
-
Ezekiel Dohmen authoredEzekiel Dohmen authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
rts-logger.c 8.52 KiB
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/kthread.h> // for threads
#include <linux/spinlock.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/llist.h>
#include "drv/rts-logger.h"
// Define the module metadata.
#define MODULE_NAME "rts_logger"
MODULE_AUTHOR("Ezekiel Dohmen ezekiel.dohmen@caltech.edu");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("A logging kernel module for isolated real time LIGO kernel modules");
MODULE_VERSION("0.1");
//
// Global options
//
#define MAX_MSG_TO_READ_AT_ONCE 1024
#define MAX_SYSFS_NUM_IN_SZ 12 //Max sized string we will take as input from sysfs
//
// Start Module Parameters
//
int MP_DEFAULT_LOG_LEVEL = RTSLOG_LOG_LEVEL_INFO;
module_param(MP_DEFAULT_LOG_LEVEL, int, S_IRUSR);
//Will get set to MP_DEFAULT_LOG_LEVEL on init
//but can be reset with sysfs "debug_level"
atomic_t g_log_level = ATOMIC_INIT(0);
//
// Input message buffer locking and stats
//
#define MAX_BUFFERED_MSG (1<<12) //(4096) This must be a power of 2, because we are using a fast mod below
#define MAX_MSG_LENGTH 2048
static char print_buffer[MAX_BUFFERED_MSG][MAX_MSG_LENGTH];
volatile static bool ready_for_print[MAX_BUFFERED_MSG];
static int cur_empty_index = 0;
static int next_to_print = 0;
atomic_t g_num_filled = ATOMIC_INIT(0);
spinlock_t g_check_and_dec_lock;
static int num_dropped_space = 0;
atomic_t g_dump_to_dmesg = ATOMIC_INIT(1);
//
// Sysfs data
//
static ssize_t sysfs_space_dropped(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
static ssize_t sysfs_dump_to_dmesg_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
static ssize_t sysfs_dump_to_dmesg_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
static ssize_t sysfs_debug_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
static ssize_t sysfs_debug_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
/* sysfs related structures */
static struct kobject *g_sysfs_dir = NULL;
/* individual sysfs debug attributes */
static struct kobj_attribute sysfs_space_dropped_attr = __ATTR(space_dropped, 0444, sysfs_space_dropped, NULL);
static struct kobj_attribute sysfs_dump_to_dmesg_attr = __ATTR(dump_to_dmesg, 0644, sysfs_dump_to_dmesg_show, sysfs_dump_to_dmesg_store);
static struct kobj_attribute sysfs_debug_level_attr = __ATTR(debug_level, 0644, sysfs_debug_level_show, sysfs_debug_level_store);
/* group attributes together for bulk operations */
static struct attribute *g_sysfs_fields[] = {
&sysfs_space_dropped_attr.attr,
&sysfs_dump_to_dmesg_attr.attr,
&sysfs_debug_level_attr.attr,
NULL,
};
static struct attribute_group g_attr_group = {
.attrs = g_sysfs_fields,
};
//
// Printer thread
//
static struct task_struct *printer_thread;
static char thread_name[] = {"printer_thread"};
void rtslog_print(int level, const char * fmt, ...)
{
int index;
va_list args;
if (level < atomic_read(&g_log_level) ) return;
va_start(args, fmt);
spin_lock(&g_check_and_dec_lock); //Lock so we can get our index
//If we are full return
if(atomic_read(&g_num_filled) == MAX_BUFFERED_MSG)
{
++num_dropped_space;
spin_unlock(&g_check_and_dec_lock);
return;
}
index = cur_empty_index;
cur_empty_index = ((cur_empty_index + 1) & (MAX_BUFFERED_MSG - 1));
atomic_inc(&g_num_filled); //If we have two threads that mark ready, but have not finished writing to the print buffer we have an issue
spin_unlock(&g_check_and_dec_lock); //We unlock here
vsnprintf(print_buffer[index], MAX_MSG_LENGTH, fmt, args);
ready_for_print[index] = 1;
va_end(args);
}
EXPORT_SYMBOL(rtslog_print);
int rtslog_get_num_dropped_space( void )
{
return num_dropped_space;
}
EXPORT_SYMBOL(rtslog_get_num_dropped_space);
int rtslog_get_num_ready_to_print( void)
{
return atomic_read(&g_num_filled);
}
EXPORT_SYMBOL(rtslog_get_num_ready_to_print);
int rtslog_set_log_level(int new_level)
{
if(new_level < RTSLOG_LOG_LEVEL_DEBUG || new_level > RTSLOG_LOG_LEVEL_ERROR) return 0;
atomic_set(&g_log_level, new_level);
return 1;
}
EXPORT_SYMBOL(rtslog_set_log_level);
int rtslog_get_log_level( void )
{
return atomic_read(&g_log_level);
}
EXPORT_SYMBOL(rtslog_get_log_level);
int printer_thread_fn( void * pv )
{
int num_read = 0;
while(!kthread_should_stop()) {
while(!kthread_should_stop() && atomic_read(&g_num_filled) > 0 && num_read < MAX_MSG_TO_READ_AT_ONCE)
{
//If num filled is ready but ready_for_print is not, we are waiting for
//the vsnprintf to complete
if(ready_for_print[next_to_print] == 0)
{
schedule();
num_read++;
continue;
}
if ( atomic_read(&g_dump_to_dmesg) )
{
printk("%s", print_buffer[next_to_print]);
}
ready_for_print[next_to_print] = 0;
next_to_print = ((next_to_print + 1) & (MAX_BUFFERED_MSG - 1));
atomic_dec(&g_num_filled);
num_read++;
}
num_read = 0;
//If there are no mesages ready for printing, we can give up the CPU
if( atomic_read(&g_num_filled) == 0)
usleep_range(499, 1000);
}
return 0;
}
static int __init logger_init(void)
{
int ret;
// Set the default log level and other defaults
atomic_set(&g_log_level, (int)MP_DEFAULT_LOG_LEVEL);
spin_lock_init(&g_check_and_dec_lock);
memset((void*)ready_for_print, 0, sizeof(bool) * MAX_BUFFERED_MSG);
//
// Sysfs init
//
g_sysfs_dir = kobject_create_and_add("rts_logger", kernel_kobj);
if (g_sysfs_dir == NULL) {
printk(KERN_ERR " - " MODULE_NAME "Could not create /sys/kernel/rts_logger directory!\n");
return -EPERM;
}
if (sysfs_create_group(g_sysfs_dir, &g_attr_group) != 0) {
printk(KERN_ERR " - " MODULE_NAME "Could not create /sys/kernel/rts_logger/... fields!\n");
ret = -EPERM;
goto out_remove_sysfs;
}
//
// Start the printer kthread
printer_thread = kthread_run(printer_thread_fn, NULL, thread_name);
if( !printer_thread )
{
printk(KERN_ERR " - " MODULE_NAME " - Could not create the reader kthread.\n");
return -1;
}
printk(MODULE_NAME " - Successfully started the " MODULE_NAME " kernel module.\n");
return 0;
// error case cleanup
out_remove_sysfs:
kobject_del(g_sysfs_dir);
return ret;
}
static void __exit logger_exit(void)
{
//Stop the printer thread
kthread_stop(printer_thread);
//Clean up sysfs
sysfs_remove_group(g_sysfs_dir, &g_attr_group);
kobject_del(g_sysfs_dir);
printk(MODULE_NAME " - Shutting down.\n");
return;
}
module_init(logger_init);
module_exit(logger_exit);
//
// Sysfs related data/code
//
bool parseInt(const char *buf, size_t count, int * result, int * error_code)
{
char localbuff[MAX_SYSFS_NUM_IN_SZ] = {0,};
if(count > MAX_SYSFS_NUM_IN_SZ)
{
*error_code = -E2BIG;
return false;
}
memcpy(localbuff, buf, count);
localbuff[count] = '\0';
if ((*error_code = kstrtoint(localbuff, 10, result)) != 0) {
return false ;
}
return true;
}
static ssize_t sysfs_space_dropped(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", num_dropped_space);
}
static ssize_t sysfs_dump_to_dmesg_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", atomic_read(&g_dump_to_dmesg) );
}
static ssize_t sysfs_dump_to_dmesg_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
int enabled, res;
if( !parseInt(buf, count, &enabled, &res) ) return res;
if(enabled >= 1)
atomic_set(&g_dump_to_dmesg, 1);
else
atomic_set(&g_dump_to_dmesg, 0);
return (ssize_t) count;
}
static ssize_t sysfs_debug_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", atomic_read(&g_log_level));
}
static ssize_t sysfs_debug_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
int new_level, res;
if( !parseInt(buf, count, &new_level, &res) ) return res;
if(new_level < RTSLOG_LOG_LEVEL_DEBUG || new_level > RTSLOG_LOG_LEVEL_ERROR) return -EINVAL;
atomic_set(&g_log_level, new_level);
return (ssize_t) count;
}