Мигаем светодиодом из модуля ядра Linux

geek.png

Всем привет. В этой статье хочу поделиться опытом создания простого модуля ядра Linux. Статья будет полезна тем, кто хотел бы понять как писать модули ядра, но не знает с чего начать.

Мне давно хотелось разобраться в этой теме, но до недавнего времени не знал как к ней подойти. Хотелось, чтобы модуль был достаточно простым, но сложнее чем сообщение «Hello world!» выведенное в log файле. В итоге я решил попробовать помигать светодиодом. Дополнительная цель была вывести параметр отвечающий за частоту мигания в sysfs.
Для проведения эксперимента я использовал плату Orange Pi One с Ubuntu Linux на борту (версия ядра 3.4.113). Для того чтобы собрать модуль ядра вам понадобиться компилятор gcc, утилита make и заголовочные файлы ядра. Чтобы установить заголовочные файлы запустите следующую команду:

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


Далее я разберу на мой взгляд самые интересные части модуля. В целях экономии места весь код тут приводить не буду, он вместе с make файлом доступен на github.

В модуле я использовал заголовочные файлы:

#include 
#include 
#include 
#include 
#include 


kernel.h и module.h нужно включать всегда при написании модуля ядра, gpio.h собственно отвечает за работу с GPIO, hrtimer.h (high resolution timer) — заголовочный файл таймера, moduleparam.h — нужен для вывода параметров в sysfs.

Чтобы не светить свои переменные и функции в ядро системы, все они должны быть описаны как static. На всякий случай отмечу, что ядро написано на C и static, в отличие от С++ означает то, что объект доступен только внутри исполняемого файла.

Точкой входа является:

static int blink_module_init(void)


Здесь я инициализирую переменные которые в дальнейшем буду использовать в том числе:

gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);


ktime_set инициализирует тип данных ktime_t задавая ему нужное количество секунд (gpio_blink_interval_s) и наносекунд (0). В дальнейшем эту переменную будет использовать таймер.

Далее идет запрос на использование GPIO:

err = gpio_request(BLINK_PIN_NR, "blink_led");


Эта функция в случае успеха возвращает 0, так что в дальнейшем я проверяю что она вернула. Далее выбранный пин нужно установить на вывод сигнала и указать значение по умолчанию.

err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW);


Если никаких ошибок не было, то инициализирую и запускаю таймер

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);


Функция обратного вызова таймера будет gpio_blink_timer_callback. В этой функции я меняю значение пина на противоположное

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


задаю когда таймер должен сработать в следующий раз

hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval);


и возвращаю HRTIMER_RESTART.

Теперь разберем как показать какую нибудь переменную в sysfs. Для этого я использую макрос

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


Первый параметр этого макроса — имя файла в sysfs. Второй — структура данных содержащая функции обратного вызова. Третий параметр — указатель на реальную переменную и четвертый права доступа к файлу в sysfs.

Функции из kp_ops вызываются когда пользователь меняет значения sysfs файла или читает его значение. Вот как я их инициализировал:

static const struct kernel_param_ops kp_ops = 
{
        .set = &set_blink_interval,
        .get = &get_blink_interval
};


В данном случае интерес представляет set, так как в нем устанавливается новое значение gpio_timer_interval.

gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0); 


В точке выхода я очищаю все используемые ресурсы

static void blink_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");
}


Точку входа и выхода обязательно нужно указать в соответствующих макросах

module_init(blink_module_init);
module_exit(blink_module_exit);


Вроде описал все важные моменты. Если у читателей возникнут какие-то вопросы с удовольствием отвечу на них в комментариях.

© Geektimes