Модули ядра Linux: таймеры и GPIO
17 января 2022
По традиции, при изучении нового языка программирования первой пишется программа, показывающая сообщение «Hello world». В мире электроники есть аналогичная традиция, только вместо вывода сообщения нужно помигать светодиодом. При изучении модулей ядра Linux (часть 1, часть 2) мы как-то обошли стороной этот важнейший этап. Пришло время исправиться, и написать модуль, мигающий светодиодом.
Сразу перейдем к исходному коду модуля:
#include <linux/gpio.h>
#define LED 14
static struct timer_list blink_timer;
static int led_status = 0;
static void blink_timer_func(struct timer_list *unused)
{
gpio_set_value(LED, led_status);
led_status = !led_status;
blink_timer.expires = jiffies + (1*HZ);
add_timer(&blink_timer);
}
int init_module(void)
{
int ret = 0;
// register, turn off
ret = gpio_request_one(LED, GPIOF_OUT_INIT_LOW, "led1");
if (ret) {
printk(KERN_ERR "Unable to request GPIOs: %d\n", ret);
return ret;
}
timer_setup(&blink_timer, blink_timer_func, 0);
led_status = 0;
blink_timer.expires = jiffies + (1*HZ);
add_timer(&blink_timer);
return ret;
}
void cleanup_module(void)
{
del_timer_sync(&blink_timer);
// turn LED off
gpio_set_value(LED, 0);
// unregister GPIO
gpio_free(LED);
}
MODULE_LICENSE("GPL");
С GPIO мы уже работали в рамках заметки GPIO-пины Raspberry Pi и их использование из Python. Вспомним, какой пин где находится:
Это иллюстрация для Raspberry Pi 2, но у 3-ей и 4-ой малины пины расположены так же. По коду не сложно понять, что он мигает светодиодом на 14-ом пине.
Fun fact! В Raspbian есть команда pinout
. Она очень наглядно выводит в консоль информацию о доступных пинах, и не только он них.
Для работы с GPIO модуль использует процедуры gpio_request_one
, gpio_free
и gpio_set_value
. Думаю, что в пояснениях они не нуждаются. Если вам хочется больше узнать про GPIO, рекомендую почитать Documentation/driver-api/gpio/ в исходном коде ядра Linux.
Для изменения состояния пина модуль использует таймеры. Это новый для нас примитив, так что остановимся на нем поподробнее.
Таймеру соответствует структура timer_list
. Инициализация происходит при помощи процедуры timer_setup
, а уничтожение — при помощи del_timer_sync
. Процедура add_timer
позволяет запланировать выполнение колбэка, переданного вторым аргументом в timer_setup
. Время срабатывания колбэка определяется значением поля expires
структуры timer_list
.
Время в ядре Linux измеряется в так называемых jiffies. Это время с момента запуска системы. Единица измерения jiffies зависит от платформы. Макрос HZ
хранит количество jiffies в одной секунде. Текущее время можно получить через глобальную переменную jiffies
.
Таким образом, код:
add_timer(&blink_timer);
… говорит таймеру сработать через одну секунду.
Указанный колбэк, в нашем случае — blink_timer_func
, выполняется в контексте прерывания (interrupt context). Это означает, что колбэк должен вернуть управление как можно быстрее. В нем не допускается делать блокирующие вызовы, такие как msleep
или mutex_lock
, ровно как и долго крутиться в цикле.
Функции printk
, add_timer
, del_timer
и kfree
всегда безопасно вызывать из любого контекста. Работа с GPIO на большинстве платформ реализована, как обращение к памяти, и потому также не является блокирующей. Для пина, работа с которым на текущей платформе приводит к блокировкам, процедура gpio_cansleep
возвращает ненулевое значение.
В качестве домашнего задания убедитесь, что на Raspberry Pi безопасно работать с GPIO в контексте прерывания. Полная версия исходников доступна на GitHub. А на сегодня это все. Казалось бы, рассмотренный модуль был совсем простым. Но смотрите, как много нового мы узнали!
Дополнение: Модули ядра Linux: обработка прерываний
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.