Sending Signal from Linux Device Driver to User Space – Linux Device Driver Tutorial Part 25

This article is a continuation of the  Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Sending Signal from Linux Device Driver to User Space – Linux Device Driver Tutorial Part 25.

You can also read Sysfs, Procfs, Workqueue, Completion, Softirq, and threaded IRQ in the Linux device driver.

Prerequisites

In the example section, we explained signals using the interrupt program. So I would recommend you to explore interrupts using the below links before starting this.

  1. Interrupts Concepts
  2. Interrupts Examples Program
  3. IOCTL Tutorial

Signals

Introduction

Generally, A signal is an action that is intended to send a particular message. It can be sound, gesture, event, etc. Below are the normal signals which we are using the day to day life.

  • When we take money in ATM, we will get a message (SMS).
  • Calling someone by making a sound or gesture.
  • Microwave oven making a sound when it finishes its job.
  • etc.

What about the signal in Linux? Signals are a way of sending simple messages which are used to notify a process or thread of a particular event. In Linux, there are many processes that will be running at a time. We can send a signal from one process to another process. Signals are one of the oldest inter-process communication methods. These signals are asynchronous. Like User space signals, can we send a signal to userspace from kernel space? Yes, why not. We will see the complete Signals in upcoming tutorials. In this tutorial, we will learn how to send a signal from Linux Device Driver to User Space.

Sending Signal from Linux Device Driver to User Space

Using the following steps easily we can send the signals.

  1. Decide the signal that you want to send.
  2. Register the user space application with the driver.
  3. Once something happened (in our example we used interrupts) send signals to userspace.
  4. Unregister the user space application when you have done with it.

Decide the signal that you want to send

First, select the signal number which you want to send. In our case, we are going to send signal 44.

Example:

#define SIGETX    44

Register the user space application with the driver

Before sending the signal, your device driver should know to whom it needs to send the signal. For that, we need to register the process to the driver. So we need to send the PID to the driver first. Then that driver will use the PID and sends the signal. You can register the application PID in anyways like IOCTL, Open/read/write call. In our example, we are going to register using IOCTL.

Example:

static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    if (cmd == REG_CURRENT_TASK) {
        printk(KERN_INFO "REG_CURRENT_TASK\n");
        task = get_current();
        signum = SIGETX;
    }
    return 0;
}

Send signals to user space

After registering the application to the driver, the driver can able to send the signal when it wants to. In our example, we will send the signal when we get the interrupt.

Example:

//Interrupt handler for IRQ 11. 
static irqreturn_t irq_handler(int irq,void *dev_id) {
    struct siginfo info;
    printk(KERN_INFO "Shared IRQ: Interrupt Occurred");
    
    //Sending signal to app
    memset(&info, 0, sizeof(struct siginfo));
    info.si_signo = SIGETX;
    info.si_code = SI_QUEUE;
    info.si_int = 1;

    if (task != NULL) {
        printk(KERN_INFO "Sending signal to app\n");
        if(send_sig_info(SIGETX, &info, task) < 0) {
            printk(KERN_INFO "Unable to send signal\n");
        }
    }
    return IRQ_HANDLED;
}

Unregister the user space application

When you are done with your task, you can unregister your application. Here we are unregistering when that application closes the driver.

Example:

static int etx_release(struct inode *inode, struct file *file)
{
    struct task_struct *ref_task = get_current();
    printk(KERN_INFO "Device File Closed...!!!\n");
    
    //delete the task
    if(ref_task == task) {
        task = NULL;
    }
    return 0;
}

Device Driver Source Code

The complete device driver code is given below. In this source code, When we read the device file (/dev/etx_device) interrupt will hit (To understand interrupts in Linux go to this tutorial). Whenever interrupt hits, I’m sending the signal to the userspace application who registered already. Since it is a tutorial post, I’m not going to do any job in the interrupt handler except sending the signal.

driver.c

[Get the source code from GitHub]

/***************************************************************************//**
*  \file       driver.c
*
*  \details    Simple Linux device driver (Signals)
*
*  \author     EmbeTronicX
*
* *******************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include<linux/slab.h>                 //kmalloc()
#include<linux/uaccess.h>              //copy_to/from_user()
#include <linux/ioctl.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/err.h>
#define SIGETX 44
 
#define REG_CURRENT_TASK _IOW('a','a',int32_t*)
 
#define IRQ_NO 11
 
/* Signaling to Application */
static struct task_struct *task = NULL;
static int signum = 0;
 
int32_t value = 0;
 
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
 
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
 
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .unlocked_ioctl = etx_ioctl,
        .release        = etx_release,
};
 
//Interrupt handler for IRQ 11. 
static irqreturn_t irq_handler(int irq,void *dev_id) {
    struct siginfo info;
    printk(KERN_INFO "Shared IRQ: Interrupt Occurred");
    
    //Sending signal to app
    memset(&info, 0, sizeof(struct siginfo));
    info.si_signo = SIGETX;
    info.si_code = SI_QUEUE;
    info.si_int = 1;
 
    if (task != NULL) {
        printk(KERN_INFO "Sending signal to app\n");
        if(send_sig_info(SIGETX, &info, task) < 0) {
            printk(KERN_INFO "Unable to send signal\n");
        }
    }
 
    return IRQ_HANDLED;
}
 
static int etx_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device File Opened...!!!\n");
    return 0;
}
 
static int etx_release(struct inode *inode, struct file *file)
{
    struct task_struct *ref_task = get_current();
    printk(KERN_INFO "Device File Closed...!!!\n");
    
    //delete the task
    if(ref_task == task) {
        task = NULL;
    }
    return 0;
}
 
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    printk(KERN_INFO "Read Function\n");
    asm("int $0x3B");  //Triggering Interrupt. Corresponding to irq 11
    return 0;
}

static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    printk(KERN_INFO "Write function\n");
    return 0;
}
 
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    if (cmd == REG_CURRENT_TASK) {
        printk(KERN_INFO "REG_CURRENT_TASK\n");
        task = get_current();
        signum = SIGETX;
    }
    return 0;
}
 
 
static int __init etx_driver_init(void)
{
    /*Allocating Major number*/
    if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
            printk(KERN_INFO "Cannot allocate major number\n");
            return -1;
    }
    printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
 
    /*Creating cdev structure*/
    cdev_init(&etx_cdev,&fops);
 
    /*Adding character device to the system*/
    if((cdev_add(&etx_cdev,dev,1)) < 0){
        printk(KERN_INFO "Cannot add the device to the system\n");
        goto r_class;
    }
 
    /*Creating struct class*/
    if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
        printk(KERN_INFO "Cannot create the struct class\n");
        goto r_class;
    }
 
    /*Creating device*/
    if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
        printk(KERN_INFO "Cannot create the Device 1\n");
        goto r_device;
    }
 
    if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "etx_device", (void *)(irq_handler))) {
        printk(KERN_INFO "my_device: cannot register IRQ ");
        goto irq;
    }
 
    printk(KERN_INFO "Device Driver Insert...Done!!!\n");
    return 0;
irq:
    free_irq(IRQ_NO,(void *)(irq_handler));
r_device:
    class_destroy(dev_class);
r_class:
    unregister_chrdev_region(dev,1);
    return -1;
}
 
static void __exit etx_driver_exit(void)
{
    free_irq(IRQ_NO,(void *)(irq_handler));
    device_destroy(dev_class,dev);
    class_destroy(dev_class);
    cdev_del(&etx_cdev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
 
module_init(etx_driver_init);
module_exit(etx_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("A simple device driver - Signals");
MODULE_VERSION("1.20");

Makefile

obj-m += driver.o

KDIR = /lib/modules/$(shell uname -r)/build

all:
    make -C $(KDIR) M=$(shell pwd) modules

clean:
    make -C $(KDIR) M=$(shell pwd) clean

Application Source Code

This application registers with the driver using IOCTL. Once it registered, it will be waiting for the signal from the driver. If we want to close this application we need to press CTRL+C. Because it will run infinitely. We have installed a CTRL+C signal handler.

test_app.c

/***************************************************************************//**
*  \file       test_app.c
*
*  \details    Userspace application to test the Device driver
*
*  \author     EmbeTronicX
*
* *******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>
 
#define REG_CURRENT_TASK _IOW('a','a',int32_t*)
 
#define SIGETX 44
 
static int done = 0;
int check = 0;
 
void ctrl_c_handler(int n, siginfo_t *info, void *unused)
{
    if (n == SIGINT) {
        printf("\nrecieved ctrl-c\n");
        done = 1;
    }
}
 
void sig_event_handler(int n, siginfo_t *info, void *unused)
{
    if (n == SIGETX) {
        check = info->si_int;
        printf ("Received signal from kernel : Value =  %u\n", check);
    }
}
 
int main()
{
    int fd;
    int32_t value, number;
    struct sigaction act;
 
    printf("*********************************\n");
    printf("*******WWW.EmbeTronicX.com*******\n");
    printf("*********************************\n");
 
    /* install ctrl-c interrupt handler to cleanup at exit */
    sigemptyset (&act.sa_mask);
    act.sa_flags = (SA_SIGINFO | SA_RESETHAND);
    act.sa_sigaction = ctrl_c_handler;
    sigaction (SIGINT, &act, NULL);
 
    /* install custom signal handler */
    sigemptyset(&act.sa_mask);
    act.sa_flags = (SA_SIGINFO | SA_RESTART);
    act.sa_sigaction = sig_event_handler;
    sigaction(SIGETX, &act, NULL);
 
    printf("Installed signal handler for SIGETX = %d\n", SIGETX);
 
    printf("\nOpening Driver\n");
    fd = open("/dev/etx_device", O_RDWR);
    if(fd < 0) {
            printf("Cannot open device file...\n");
            return 0;
    }
 
    printf("Registering application ...");
    /* register this task with kernel for signal */
    if (ioctl(fd, REG_CURRENT_TASK,(int32_t*) &number)) {
        printf("Failed\n");
        close(fd);
        exit(1);
    }
    printf("Done!!!\n");
   
    while(!done) {
        printf("Waiting for signal...\n");
 
        //blocking check
        while (!done && !check);
        check = 0;
    }
 
    printf("Closing Driver\n");
    close(fd);
}

Building Driver and Application

  • Build the driver by using Makefile (sudo make)
  • Use the below line in the terminal to compile the user space application.

gcc -o test_app test_app.c

Execution (Output)

As of now, we have driver.ko and test_app. Now we will see the output.

  • Load the driver using sudo insmod driver.ko
  • Run the application (sudo ./test_app)
*********************************
*******WWW.EmbeTronicX.com*******
*********************************
Installed signal handler for SIGETX = 44

Opening Driver
Registering application ...Done!!!
Waiting for signal...
  • This application will be waiting for the signal
  • To send the signal from driver to app, we need to trigger the interrupt by reading the driver (sudo cat /dev/etx_device).
  • Now see the Dmesg (dmesg)
Major = 246 Minor = 0
Device Driver Insert...Done!!!
Device File Opened...!!!
REG_CURRENT_TASK
Device File Opened...!!!
Read Function
Shared IRQ: Interrupt Occurred
Sending signal to app
Device File Closed...!!!
  • As per the print, the driver has sent the signal. Now check the app.
Received signal from kernel : Value = 1
  • So the application also got the signal.
  • Close the application by pressing CTRL+C
  • Unload the module using sudo rmmod driver

Note: If you use newer version of kernel, then you might face some issues. Please go through the below comments and fix it.

In our next tutorial, we will discuss the kernel timer in the Linux device driver. If you want real hardware interrupt, you can implement this same signaling mechanism in the GPIO interrupt Linux driver.

Please find the other Linux device driver tutorials here.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL TutorialsArduino Tutorials


Discover more from EmbeTronicX

Subscribe to get the latest posts sent to your email.

15 thoughts on “Sending Signal from Linux Device Driver to User Space – Linux Device Driver Tutorial Part 25”

  1. Hi,

    I wanted to have some more clarity regarding the structure “siginfo” and functions such as “send_sig_info”

    Have these structures and functions been discussed before? Asking this question as I don’t see any introduction to these parameters.

    Reply
  2. hi,SLR,
    when i run test_app,the terminal is show “wait signal..” forever,so i need to open another terminal to run “cat ./test_app /dev/xxx?
    i usually used MobaXterm connect my linux host by ssh.
    thank you very much.

    Reply
    • i find my error,forgiving “!check”,but when i use “cat /dev/xxx” ,it can not execte command.
      # ./drv_test /dev/sculldev
      —DRIVER TEST—
      file has opend
      register done
      wait for signal
      cat /dev/sculldev

      Reply
  3. After loading the driver, I am running the userSpace program. The signal received once and after that, I am getting a segmentation fault.
    Saying that:::segfault at 24 ipb777d918 sp bfe11190 error 6 in ld-2.15.so. I am using an older kernel version and my kernel version is 3.13. And the interrupt I am using is keypress interrupt which has IRQ_NO 1.

    Reply
    • Are you getting this error when you receive the 2nd signal? Just to isolate the issue, try without the application and see are you facing this issue in the kernel (Is ISR triggered properly?).

      Reply
      • Without the application, I am not facing any issues. The driver is loaded and no error messages. After running the user space application the signal is sent once and I am getting a segfault. The below message is the dmesg. And I highlighted the segfault message.

        [ 428.883406] Interrupt is triggered,count:82
        [ 429.036766] Interrupt is triggered,count:83
        [ 429.104442] Opening the file
        [ 429.107849] called by userSpace
        [ 432.122993] Interrupt is triggered,count:84
        [ 432.123119] sending the signal to userspace
        [ 432.143175] show_signal_msg: 126 callbacks suppressed
        [ 432.143210] a.out[2542]: segfault at 24 ip b77d3918 sp bfec5f30 error 6 in ld-2.15.so[b77d3000+1000]
        [ 432.149043] closing the file
        [ 432.373662] Interrupt is triggered,count:85
        [ 432.406732] Interrupt is triggered,count:86
        [ 432.440123] Interrupt is triggered,count:87

        Reply
  4. getting error

    /home/bucky/Desktop/new_driver/driver.c: In function ‘irq_handler’:
    /home/bucky/Desktop/new_driver/driver.c:65:27: error: storage size of ‘info’ isn’t known
    struct kernel_siginfo info;//defined in signal.h

    Reply
    • Hi Shubham,
      You might be using the newer kernel than we have used it. You should port our old kernel changes to new kernel. We have provided our changes below.

      Include this header file (#include "linux/sched/signal.h")
      Then typecast the info argument to (struct kernel_siginfo *)

      Please apply this patch.

      + #include "linux/sched/signal.h"
      - if(send_sig_info(SIGETX, &info, task) < 0) {
      + if(send_sig_info(SIGETX, (struct kernel_siginfo *)&info, task) < 0) {

      After applying this you, might face problem with interrupt too. Please refer this link to fix that issue.

      Reply
  5. Getting following error in dmesg logs
    do_IRQ: 7.59 No irq handler for vector

    while triggering interrupt
    sudo cat /dev/etx_device

    Tried following steps to get rid of this error

    sudo gedit /etc/default/grub
    change GRUB_CMDLINE_LINUX_DEFAULT=”quiet splash pci=nomsi,noaer”
    3.sudo update-grub

    but its not working

    kernel version :- 4.14.0-041400-generic

    Reply
  6. Hello,

    Interesting post. Could you please share the application as well?
    I think the driver code has been copied over.

    Thanks,
    Mihaita

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from EmbeTronicX

Subscribe now to keep reading and get access to the full archive.

Continue reading