ToBe | Howto
Linux Kernel Module
Preparing
Configuring build host
sudo apt-get install linux-headers-`uname -r`
Note: on Raspberry Pi, kernel headers may not be available
sudo rpi-update sudo reboot wget https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source -O /usr/bin/rpi-source && sudo chmod +x /usr/bin/rpi-source && /usr/bin/rpi-source -q --tag-update sudo apt-get install bc rpi-source --skip-gcc
Building
Creating a Kernel Module
A module is declared as follow:
my-module.c
#include <linux/init.h>
#include <linux/module.h>
static int __init init(void) {
printk(KERN_INFO "my-module: starting...");
return 0;
}
static void __exit exit(void) {
printk(KERN_INFO "my-module: ...stopping.");
}
module_init(init);
module_exit(exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bertrand Tognoli");
MODULE_DESCRIPTION("Some useless module");
MODULE_VERSION("1.0");
Makefile
KDIR ?= /lib/modules/$(shell uname -r)/build obj-m := my-module.o all: $(MAKE) -C $(KDIR) M=$$PWD install: all $(MAKE) -C $(KDIR) M=$$PWD modules_install clean: $(MAKE) -C $(KDIR) M=$$PWD clean .PHONY: all install clean
To test that module:
# Building the module make # Installing the module sudo make install # Starting the module sudo insmod my-module # Deleting the module sudo rmmod my-module
Installing
Deploying Kernel module
Kernel modules need to be built from a particular Kernel version. When kernel is upgraded, the module needs to be re-built
DKMS helps to automate that process
sudo apt install dkms
dkms.conf
PACKAGE_NAME=tobe-pilot
PACKAGE_VERSION=1.1
MAKE="make -C kmod KDIR=/lib/modules/${kernelver}/build"
CLEAN="make -C kmod clean"
BUILT_MODULE_NAME=tobe-pilot
BUILT_MODULE_LOCATION=kmod
DEST_MODULE_LOCATION=/kernel/drivers/misc/
AUTOINSTALL=yes
# Create DKMS module directory sudo mkdir /usr/src/my-module-1.0 sudo cp -r dkms.conf kmod /usr/src/tobe-pilot-1.1 sudo dkms add -m my-module -v 1.0 sudo dkms build -m my-module -v 1.0 sudo dkms install -m my-module -v 1.0
sudo mkdir /usr/src/tobe-pilot-1.1-x sudo cp -r dkms.conf kmod /usr/src/tobe-pilot-1.1-x sudo dkms add -m tobe-pilot -v 1.1-x ll /lib/modules/4.19.97+/updates sudo dkms add -m tobe-pilot -v 1.1-x sudo dkms build -m tobe-pilot -v 1.1-x sudo dkms install -m tobe-pilot -v 1.1-x /var/lib/dkms/tobe-pilot/1.1/4.19.97+/armv6l/log/make.log /var/lib/dkms/tobe-pilot/1.1/4.19.97+/armv6l/module/tobe-pilot.ko /lib/modules/4.19.97+/extra/tobe-pilot.ko
Module data
Configuring the module
Parameter can be accessesed via /sys/module pseudo filesystem
my-module.c
static char *param = "unset"; module_param(param, charp, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(param, "Module parameter (default=unset)");
Note: A module parameter can be changed from userspace (if allowed), but module itself is not notified about the change.
cat /sys/module/my-module/parameters/param echo "set" > /sys/module/my-module/parameters/param
SysFS virtual filesystem expose an interface to kernel modules kobject structure.
my-module.c
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/string.h>
static int data;
static struct device *dev;
static struct kobject *obj;
static ssize_t show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
return sprintf(buf, "%d\n", data);
}
static ssize_t store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
int ret = kstrtoint(buf, 10, &data);
return (ret < 0 ? ret : count);
}
static struct kobj_attribute attr = __ATTR(data, 0664, show, store);
static struct attribute *attrs[] = { &attr.attr, NULL };
static struct attribute_group attr_group = { .attrs = attrs };
static int __init example_init(void) {
dev = root_device_register("tobe-pilot");
obj = kobject_create_and_add("obj", dev->kobj);
if (!obj)
return -ENOMEM;
if (sysfs_create_group(obj, &attr_group)) {
printk(KERN_WARN "KOBJ: creating sysfs failed.");
kobject_put(obj);
return -ENOMEM;
}
return 0;
}
static void __exit exit(void) {
kobject_put(obj);
root_device_unregister(dev);
}
Kernel API
Kernel SW development
Delay
Delay function will block caller for given time. They are only recommended for short delays (e.g. <10us)
ndelay(nsecs); // May not be available on all platforms udelay(usecs); mdelay(msecs); // Not recommended to lock kernel that long
Sleep
Sleep function are backed by timers (or hrtimers), so they do not block the kernel. hrtimers (usleep_range) is used for timers < 5~10ms
usleep_range(min, max); // Uses HR timer msleep(msecs); msleep_interruptible(msecs); // If timer could be interrupted by another signal
Timers
my-module.c
#include <linux/timer.h>
static struct timer_list tim;
static void callback(unsigned long data) {
mod_timer(&tim, jiffies + HZ);
}
static int __init init(void) {
init_timer(&tim);
tim.function = callback;
tim.data = 0;
tim.expires = jiffies + HZ;
add_timer(&tim);
}
static void __exit exit(void) {
del_timer(&tim);
}
my-module.c
#include <linux/kthread.h>
#include <linux/delay.h>
struct task_struct *task;
int thread(void *data) {
while(1) {
msleep(500);
if (kthread_should_stop()) break;
}
return 0;
}
void init(void) {
task = kthread_run(task, NULL, THREAD_NAME);
}
void exit(void) {
kthread_stop(task);
}
static DEFINE_SPINLOCK(lock);
static enum { IDLE, ACTIVE } tskStatus;
void tskSetStatus(void) {
unsigned long flags;
spin_lock_irqsave(&lock, flags);
tskStatus = ACTIVE;
spin_unlock_irqrestore(&lock, flags);
}
my-module.c
#include <linux/interrupt.h>
static irqreturn_t intr(int irq, void *dev) {
// handle irq
return IRQ_HANDLED;
}
static int __init init(void) {
if ((err = request_irq(gpio_to_irq(0), intr, IRQF_SHARED | IRQF_TRIGGER_RISING, "intr", dev)) != 0) {
return err;
}
return 0;
}
static void __exit exit(void) {
free_irq(gpio_to_irq(0), dev);
}
Userspace
echo 0 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio0/direction cat /sys/class/gpio/gpio0/value
Kernelspace
my-module.c
#include <linux/gpio.h>
static void gpioClear(void) {
gpio_set_value(0, 0);
}
static void gpioRead(void) {
if (gpio_get_value(0) == 0)
printk(KERN_INFO "Gpio: gpio0 is low.");
else
printk(KERN_INFO "Gpio: gpio0 is high.");
}
static int __init init(void) {
int err;
if ((err = gpio_request(0, "gpio0")) != 0)
return err;
// if ((err = gpio_direction_output(0, 1)) != 0) {
if ((err = gpio_direction_input(0)) != 0) {
gpio_free(0);
return err;
}
return 0;
}
static void __exit exit(void) {
gpio_free(0);
}
Userspace application call be invoked from Kernel context using UMH API (Unser Mode Helper)
my-module.c
#include <linux/kmod.h>
char *envp[] = {"HOME=/", "PATH=/sbin:/bin:/usr/bin", NULL};
char *argv[] = {"/bin/my-prog", "some args", NULL};
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
2021-12-07