Blink LED from the Linux kernel module

    Hello. In this article I want to share the experience of creating a simple Linux kernel module. The article will be useful to those who would like to understand how to write kernel modules, but do not know where to start.

    I have long wanted to understand this topic, but until recently did not know how to approach it. I wanted the module to be quite simple, but harder than the “Hello world!” Message displayed in the log file. In the end, I decided to try to flash the LED. An additional goal was to display the parameter responsible for the flashing frequency in sysfs.

    For the experiment, I used the Orange Pi One with Ubuntu Linux on board (kernel version 3.4.113). In order to build a kernel module, you need a gcc compiler, a make utility, and kernel header files. To install the header files run the following command:

    sudo apt-get install linux-headers-$(uname -r)
    

    Next, I will analyze in my opinion the most interesting parts of the module. In order to save space, I’ll not bring all the code here, it is available on github along with the make file .

    In the module I used header files:

    #include<linux/kernel.h>#include<linux/module.h>#include<linux/gpio.h>#include<linux/hrtimer.h>#include<linux/moduleparam.h>

    kernel.h and module.h must always be included when writing a kernel module, gpio.h is actually responsible for working with GPIO, hrtimer.h (high resolution timer) - timer header file, moduleparam.h is needed for displaying parameters in sysfs.

    In order not to shine their variables and functions in the core of the system, they should all be described as static. Just in case, I note that the kernel is written in C and static, unlike C ++, which means that the object is accessible only inside the executable file.

    Entry point is:

    staticintblink_module_init(void)

    Here I initialize the variables that I will use in the future, including:

    gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
    

    ktime_set initializes the ktime_t data type by asking it the required number of seconds (gpio_blink_interval_s) and nanoseconds (0). In the future, this variable will use the timer.

    Next comes the GPIO request:

    err = gpio_request(BLINK_PIN_NR, "blink_led");
    

    This function returns 0 if successful, so I check later what it returned. Next, the selected pin must be set to output the signal and specify the default value.

    err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW);
    

    If there were no errors, then initialize and start the timer

    hrtimer_init(&gpio_blink_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    gpio_blink_timer.function = &gpio_blink_timer_callback;
    hrtimer_start(&gpio_blink_timer, gpio_timer_interval, HRTIMER_MODE_REL);
    

    The timer callback function will be gpio_blink_timer_callback. In this function, I change the value of the pin to the opposite

    gpio_value ^= 0x01;
    gpio_set_value(BLINK_PIN_NR, gpio_value);
    

    I set when the timer should work next time

    hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval);
    

    and return HRTIMER_RESTART.

    Now we will analyze how to show some variable in sysfs. For this, I use the macro

    module_param_cb(gpio_blink_interval_s, &kp_ops, &gpio_blink_interval_s, 0660); 
    

    The first parameter of this macro is the file name in sysfs. The second is a data structure containing callback functions. The third parameter is a pointer to the real variable and the fourth file permissions in sysfs.

    Functions from kp_ops are called when the user changes the sysfs values ​​of a file or reads its value. Here is how I initialized them:

    staticconststructkernel_param_opskp_ops = 
    {
    	.set = &set_blink_interval,
    	.get = &get_blink_interval
    };
    

    In this case, of interest is set, since it sets the new value of gpio_timer_interval.

    gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0); 
    

    At the exit point, I clear all used resources.

    staticvoidblink_module_exit(void){
    	hrtimer_cancel(&gpio_blink_timer);
    	gpio_set_value(BLINK_PIN_NR, 0);
    	gpio_free(BLINK_PIN_NR);
    	printk(KERN_ALERT "Blink module unloaded\n");
    }
    

    The entry and exit points must be indicated in the corresponding macros.

    module_init(blink_module_init);
    module_exit(blink_module_exit);
    

    Like described all the important points. If readers have any questions, I’ll be happy to answer them in the comments.

    Also popular now: