По традиции, при изучении нового языка программирования первой пишется программа, показывающая сообщение «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: обработка прерываний