Skip to content
Snippets Groups Projects
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;

}