Модули ядра Linux: таймеры и GPIO

17 января 2022

По традиции, при изучении нового языка программирования первой пишется программа, показывающая сообщение «Hello world». В мире электроники есть аналогичная традиция, только вместо вывода сообщения нужно помигать светодиодом. При изучении модулей ядра Linux (часть 1, часть 2) мы как-то обошли стороной этот важнейший этап. Пришло время исправиться, и написать модуль, мигающий светодиодом.

Сразу перейдем к исходному коду модуля:

#include <linux/module.h>
#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. Вспомним, какой пин где находится:

Номера GPIO-пинов Raspberry Pi

Это иллюстрация для 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.

Таким образом, код:

    blink_timer.expires = jiffies + (1*HZ);
    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: обработка прерываний

Метки: , .