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

Всем привет. В этой статье хочу поделиться опытом создания простого модуля ядра 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 unloadedn"); } 

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

module_init(blink_module_init); module_exit(blink_module_exit); 

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

 
Источник

Читайте также