ToBe | Howto
Linux Kernel Module
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
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
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
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 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