Kernel Module

Real time interrupts and operating systems are not generally compatible. For operating systems it is needed to poll the pin which one is willing to work as an interrupt but this is not real time and generally speaking is not very CPU deficient. For this purpose a Kernel module can be inserted which can handle this as real time as possible. I needed to make an interrupt in a raspberry pi to be able to count encoder signals, the following tutorial is the ins and outs of how I achieved this.

Firstly install the latest Raspbian (I have done this tutorial with Raspbian Stretch Lite) I will also mention here that I recommend using a none X server operating system as some of the print calls from kernel you may not see them if you are in a X server OS:
https://www.raspberrypi.org/downloads/raspbian/

Once downloaded you can burn the image to a micro SD card using DiskImager in windows:
https://sourceforge.net/projects/win32diskimager/

Then insert the SD card on the Raspberry PI (I used PI3) and boot from the micro SD card. Make sure the PI is connected to the internet.

In the command line run the following commands:

# The usual update routine

apt-get update -y
apt-get upgrade -y

# Update the kernel!

rpi-update

# Get rpi-source

sudo wget https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source -O /usr/bin/rpi-source

# Make it executable

sudo chmod +x /usr/bin/rpi-source

# Tell the update mechanism that this is the latest version of the script

/usr/bin/rpi-source -q –tag-update

# Get the kernel files thingies.

rpi-source

At this point you should have all the needed components to start compiling a kernel module.

So lets start by looking at a simple Hello World kernel module:

# Create a new directory enter it.

mkdir hello
cd hello

# Crete a file named hello.c and edit it

nano hello.c

# Edit the file to have the following lines of code in it.

#include <linux/module.h>
#include <linux/kernel.h>

int hello_init(void)
{
pr_alert(“Hello World :)\n”);
return 0;
}

void hello_exit(void)
{
pr_alert(“Goodbye World!\n”);
}
module_init(hello_init);
module_exit(hello_exit);

# Save the file and exit nano

Ctrl+X (and follow the on screen options)

# Make a Makefile for compiling

nano Makefile

# Edit it as follow.

obj-m := hello.o

# save file and exit

Ctrl+X (and follow the on screen options)

# Compile and load kernel module

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
insmod hello.ko

That is it. You have mounted your first kernel module. it does nothing but it is a start.

——————————————————————————————————
——————————————————————————————————
————————- SOMETHING A BIT MORE COMPLICATED —————————
——————————————————————————————————
——————————————————————————————————

The interrupt module I made looks like this:

#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define GPIO_FOR_RX_SIGNAL 27
#define GPIO_FOR_INT_SIGNAL_2 17
#define DEV_NAME “rfrpi”
#define BUFFER_SZ 512

#ifndef IRQF_DISABLED
#define IRQF_DISABLED 0
#endif

/* Last Interrupt timestamp */
static struct timespec lastIrq_time;
static unsigned long lastDelta[BUFFER_SZ];
static int pRead;
static int pWrite;
static int wasOverflow;

static int gCounter = 0;

/* Define GPIOs for RX signal */
static struct gpio signals[] = {
{ GPIO_FOR_RX_SIGNAL, GPIOF_IN, “RX Signal” }, // Rx signal
{ GPIO_FOR_INT_SIGNAL_2, GPIOF_IN, “Int 2” },
};

/* Later on, the assigned IRQ numbers for the buttons are stored here */
static int rx_irqs[] = { -1, -1 };

/*
* The interrupt service routine called on every pin status change
*/
static irqreturn_t rx_isr(int irq, void *data)
{
struct timespec current_time;
struct timespec delta;
unsigned long ns;

getnstimeofday(&current_time);
delta = timespec_sub(current_time, lastIrq_time);
ns = ((long long)delta.tv_sec * 1000000)+(delta.tv_nsec/1000);
lastDelta[pWrite] = ++gCounter;//;ns;
getnstimeofday(&lastIrq_time);

pWrite = ( pWrite + 1 ) & (BUFFER_SZ-1);
if (pWrite == pRead) {
// overflow
pRead = ( pRead + 1 ) & (BUFFER_SZ-1);
if ( wasOverflow == 0 ) {
printk(KERN_ERR “RFRPI – Buffer Overflow – IRQ will be missed”);
wasOverflow = 1;
}
} else {
wasOverflow = 0;
}
return IRQ_HANDLED;
}

/*
* The interrupt service routine called on every pin status change
*/
static irqreturn_t int2_isr(int irq, void *data)
{
if (gpio_get_value(signals[0].gpio))
lastDelta[pWrite] = –gCounter;//;ns;
else
lastDelta[pWrite] = ++gCounter;//;ns;
getnstimeofday(&lastIrq_time);

pWrite = ( pWrite + 1 ) & (BUFFER_SZ-1);
if (pWrite == pRead) {
// overflow
pRead = ( pRead + 1 ) & (BUFFER_SZ-1);
if ( wasOverflow == 0 ) {
printk(KERN_ERR “RFRPI – Buffer Overflow – IRQ will be missed”);
wasOverflow = 1;
}
} else {
wasOverflow = 0;
}
return IRQ_HANDLED;
}

static int rx433_open(struct inode *inode, struct file *file)
{
return nonseekable_open(inode, file);
}

static int rx433_release(struct inode *inode, struct file *file)
{
return 0;
}

static ssize_t rx433_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
return -EINVAL;
}

static ssize_t rx433_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
// returns one of the line with the time between two IRQs
// return 0 : end of reading
// return >0 : size
// return -EFAULT : error
char tmp[256];
int _count;
int _error_count;

_count = 0;
if ( pRead != pWrite ) {
sprintf(tmp,”%ld\n”,lastDelta[pRead]);
_count = strlen(tmp);
_error_count = copy_to_user(buf,tmp,_count+1);
if ( _error_count != 0 ) {
printk(KERN_ERR “RFRPI – Error writing to char device”);
return -EFAULT;
}
pRead = (pRead + 1) & (BUFFER_SZ-1);
}
return _count;
}

static struct file_operations rx433_fops = {
.owner = THIS_MODULE,
.open = rx433_open,
.read = rx433_read,
.write = rx433_write,
.release = rx433_release,
};

static struct miscdevice rx433_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &rx433_fops,
};

/*
* Module init function
*/
static int __init rfrpi_init(void)
{
int ret = 0;
printk(KERN_INFO “%s\n”, __func__);

// INITIALIZE IRQ TIME AND Queue Management
getnstimeofday(&lastIrq_time);
pRead = 0;
pWrite = 0;
wasOverflow = 0;

// register GPIO PIN in use
ret = gpio_request_array(signals, ARRAY_SIZE(signals));

if (ret) {
printk(KERN_ERR “RFRPI – Unable to request GPIOs for RX Signals: %d\n”, ret);
goto fail2;
}

// Register IRQ for this GPIO
ret = gpio_to_irq(signals[0].gpio);
if(ret < 0) {
printk(KERN_ERR “RFRPI – Unable to request IRQ: %d\n”, ret);
goto fail2;
}
rx_irqs[0] = ret;
printk(KERN_INFO “RFRPI – Successfully requested RX IRQ # %d\n”, rx_irqs[0]);
ret = request_irq(rx_irqs[0], rx_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_DISABLED, “rfrpi#rx”, NULL);
if(ret) {
printk(KERN_ERR “RFRPI – Unable to request IRQ: %d\n”, ret);
goto fail3;
}

// Register IRQ for second GPIO
ret = gpio_to_irq(signals[1].gpio);
if(ret < 0) {
printk(KERN_ERR “RFRPI – Unable to request IRQ: %d\n”, ret);
goto fail2;
}
rx_irqs[1] = ret;
printk(KERN_INFO “RFRPI – Successfully requested RX IRQ # %d\n”, rx_irqs[1]);
ret = request_irq(rx_irqs[1], int2_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_DISABLED, “rfrpi#rx”, NULL);
if(ret) {
printk(KERN_ERR “RFRPI – Unable to request IRQ: %d\n”, ret);
goto fail3;
}

// Register a character device for communication with user space
misc_register(&rx433_misc_device);

return 0;

// cleanup what has been setup so far
fail3:
free_irq(rx_irqs[0], NULL);
free_irq(rx_irqs[1], NULL);

fail2:
gpio_free_array(signals, ARRAY_SIZE(signals));
return ret;
}

/**
* Module exit function
*/
static void __exit rfrpi_exit(void)
{
printk(KERN_INFO “%s\n”, __func__);

misc_deregister(&rx433_misc_device);

// free irqs
free_irq(rx_irqs[0], NULL);
free_irq(rx_irqs[1], NULL);

// unregister
gpio_free_array(signals, ARRAY_SIZE(signals));
}

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Disk91”);
MODULE_DESCRIPTION(“Linux Kernel Module for rfrpi shield”);

module_init(rfrpi_init);
module_exit(rfrpi_exit);

The Makefile for it is as follow:

ifneq (${KERNELRELEASE},)
obj-m = krfrpi.o
else
KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build
MODULE_DIR := $(shell pwd)

.PHONY: all

all: modules

.PHONY:modules

modules:
${MAKE} -C ${KERNEL_DIR} SUBDIRS=${MODULE_DIR} modules

clean:
rm -f *.o *.ko *.mod.c .*.o .*.ko .*.mod.c .*.cmd *~
rm -f Module.symvers Module.markers modules.order
rm -rf .tmp_versions
endif

Once this module is mounted it makes a file at /dev/rfrpi this file can be dumped with cat /dev/rfrpi or you can read it in a c program as follow:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

int main(int argc, char ** argv) {
int fd;
char buf[256];

fd = open(“/dev/rfrpi”, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror(“open_port: Unable to open /dev/rfrpi – “);
return(-1);
}

// Turn off blocking for reads, use (fd, F_SETFL, FNDELAY) if you want that
fcntl(fd, F_SETFL, 0);

while(1){
int n = read(fd, (void*)buf, 255);
if (n < 0) {
perror(“Read failed – “);
return -1;
} else if (n == 0) {
printf(“No data on port\n”);
sleep(1);
}
else {
buf[n] = ‘\0’;
printf(/*”%i bytes read : %s” */”%15s”/*, n*/, buf);
}
//usleep(1000);
sleep(1);
//printf(“i’m still doing something”);

}
close(fd);
return 0;
}

References:

Step by step on how to compile a kernel module:
https://raspberrypi.stackexchange.com/questions/39845/how-compile-a-loadable-kernel-module-without-recompiling-kernel#40419

Raspberry PI Interrupt example:
https://www.disk91.com/2015/technology/systems/rf433-raspberry-pi-gpio-kernel-driver-for-interrupt-management/

Article on Raspberry PI module kernel programming:
https://blog.fazibear.me/the-beginners-guide-to-linux-kernel-module-raspberry-pi-and-led-matrix-790e8236e8e9

Information on GPIO.h header file. Very good for general reference.
https://www.mjmwired.net/kernel/Documentation/gpio.txt

https://elixir.bootlin.com/linux/latest/source/include/linux/gpio.h