3

My kernel module code needs to send signal to a user land program, to transfer its execution to registered signal handler.

In fact, I have developed a C program for my embedded board which make LED turns on and off when I push BUTTON ( the input event ). On the other hand, I have just developed a simple Linux module with its basic functions ( OPEN, CLOSE, READ, WRITE ).

I just don't have any idea how to modify my principal program and my kernel module in order to arrive to my objective.

I share with you my user space program :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>

#include <linux/input.h>

#define BTN_FILE_PATH "/dev/input/event0"
#define LED_PATH "/sys/class/leds"

#define green "green"

void change_led_state(char *led_path, int led_value)
{
    char    lpath[64];
    FILE    *led_fd;

    strncpy(lpath, led_path, sizeof(lpath) - 1);
    lpath[sizeof(lpath) - 1] = '\0';

    led_fd = fopen(lpath, "w");

    if (led_fd == NULL) {
        fprintf(stderr, "simplekey: unable to access led\n");
        return;
    }

    fprintf(led_fd, "%d\n", led_value);

    fclose(led_fd);
}

void reset_leds(void)
{

    change_led_state(LED_PATH "/" green "/brightness", 0);
}

int configure_leds(void)
{
    FILE    *r_fd;
    char    *none_str = "none";

    /* Configure leds for hand control */

    r_fd = fopen(LED_PATH "/" green "/trigger", "w");




    fprintf(r_fd, "%s\n", none_str);


    fclose(r_fd);


    /* Switch off leds */
    reset_leds();

    return 0;
}

void eval_keycode(int code)
{

    static int green_state = 0;

    switch (code) {
    case 260:
        printf("BTN left pressed\n");

        /* figure out green state */

        green_state = green_state ? 0 : 1;

        change_led_state(LED_PATH "/" green "/brightness", green_state);
        break;
    }
}


int main(void)
{
    int file;
    /* how many bytes were read */
    size_t  rb;
    int ret;
    int yalv;
    /* the events (up to 64 at once) */
    struct input_event  ev[64];
    char    *str = BTN_FILE_PATH;

    printf("Starting simplekey app\n");

    ret = configure_leds();
    if (ret < 0)
        exit(1);

    printf("File Path: %s\n", str);

    if((file = open(str, O_RDONLY)) < 0) {
        perror("simplekey: File can not open");
        exit(1);
    }

    for (;;) {
        /* Blocking read */
        rb= read(file, &ev, sizeof(ev));

        if (rb < (int) sizeof(struct input_event)) {
            perror("simplekey: short read");
            exit(1);
        }

        for (yalv = 0;
            yalv < (int) (rb / sizeof(struct input_event));
            yalv++) {
            if (ev[yalv].type == EV_KEY) {
                printf("%ld.%06ld ",
                    ev[yalv].time.tv_sec,
                    ev[yalv].time.tv_usec);
                printf("type %d code %d value %d\n",
                        ev[yalv].type,
                        ev[yalv].code, ev[yalv].value);

                /* Change state on button pressed */
                if (ev[yalv].value == 0)
                    eval_keycode(ev[yalv].code);
            }
        }
    }

    close(file);

And this is the basic kernel module :

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>  
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/input.h>

MODULE_LICENSE("GPL");      
MODULE_AUTHOR("Gaston");  
MODULE_DESCRIPTION("A simple Linux char driver"); 
MODULE_VERSION("0.1"); 


ssize_t exer_open(struct inode *pinode, struct file *pfile) {

    printk(KERN_INFO "Device has been opened\n");

    return 0;
}



ssize_t exer_read(struct file *pfile, char __user *buffer, size_t length, loff_t *offset) {

    return 0;
}



ssize_t exer_write(struct file *pfile, const char __user *buffer, size_t length, loff_t *offset) {

    return 0;

}   




ssize_t exer_close(struct inode *pinode, struct file *pfile) {

    printk(KERN_INFO "Device successfully closed\n");
    return 0;
}


struct file_operations exer_file_operations = { 
    .owner = THIS_MODULE,
    .open = exer_open,
    .read = exer_read,
    .write = exer_write,
    .release = exer_close,
};


int exer_simple_module_init(void) {

    printk(KERN_INFO "Initializing the LKM\n");
    register_chrdev(240, "Simple Char Drv", &exer_file_operations);
    return 0;
}


void exer_simple_module_exit(void) {

    unregister_chrdev(240, "Simple Char Drv");
}


module_init(exer_simple_module_init);
module_exit(exer_simple_module_exit);

I hope you will help me. Thank you!

gaston
  • 405
  • 5
  • 22
  • Have you searched other questions on this topic? Googling for "linux kernel notify user space" finds several questions suggested using poll/epoll (e.g. [that one](https://stackoverflow.com/questions/24444670/event-notification-from-kernel-space-to-user-space)). [Another one](https://stackoverflow.com/a/19877614/3440745) suggests to sending a signal. – Tsyvarev Oct 31 '19 at 08:44
  • First of all, you need to implement some code, that recognizes the button press. This can be a (kernel or userspace) thread that polls some port, or an interrupt handler. That depends on the hardware you use. After that, you can keep track of the processes , that have your char device opened and, if you really do not want to use polling, send signals (with `send_sig_info()` or similar) to those processes. – Ctx Oct 31 '19 at 09:02
  • There is no reason to *"send signal from Linux kernel ... to notify about an input hardware event"*. Userspace already gets notification when you read a `struct input_event`. Did you notice that the button press is passed from the kernel to userspace as an "input event"? So you want to send a redundant *"signal"* to indicate that there's an "input event" that needs to be read? The blocking **read()** that your program already uses is all that you need to receive notification in the most timely and efficient manner. – sawdust Oct 31 '19 at 19:36

1 Answers1

0

I will concentrate on sending a signal, since that is what you asked for, although sending signals to a process is quite brutal. It would be better to implement poll and read file operations so the user code can wait for events from the device and read them.

Anyway, for sending a signal to the processes that opened the device, the things you need are:

  1. You need a struct fasync_struct * in the private data of your device:

    struct fasync_struct *pasync_queue;
    

    It needs to be initialized to NULL by some means during initialization of your device private data. How you do that is up to you.

  2. You need a fasync file operation handler pointed to by the fasync member of your struct file_operations. The implementation of the fasync handler is very simple as it just needs to call fasync_helper() using supplied parameters and a pointer to your device's private struct fasync_struct *:

    static int exer_fasync(int fd, struct file *pfile, int mode)
    {
         // N.B. Change this code to use the pasync_queue member from your device private data.
         struct fasync_struct **fapp = &pasync_queue;
         return fasync_helper(fd, pfile, mode, fapp);
    }
    
    struct file_operations exer_file_operations = { 
         .owner = THIS_MODULE,
         .open = exer_open,
         .read = exer_read,
         .write = exer_write,
         .release = exer_close,
         .fasync = exer_fasync,
    };
    
  3. Your device driver can send a SIGIO signal by calling kill_fasync() as follows:

         // N.B. Change this code to use the pasync_queue member from your device private data.
         struct fasync_struct **fapp = &pasync_queue;
         kill_fasync(fapp, SIGIO, POLL_IN);
    

    N.B. The last parameter (value POLL_IN in this case) affects the value of the si_band member of the siginfo_t that your application sees in its signal handler.

  4. Your application needs to set a signal handler for the SIGIO signal. I recommend usingsigaction() to set this up.

  5. Your application needs to set the O_ASYNC flag when it opens the device file, or set it by calling fcntl(fd, F_SETFL, O_ASYNC); after opening the device file.

Ian Abbott
  • 15,083
  • 19
  • 33