Хотели ли вы когда нибудь написать свой драйвер, для операционной системы? Думаю многие задумывались об этом, так вот, наконец вам предоставился случай. Продолжаем изучение малинки, на примере ногодрыга, на уровне ядра.

Не смотря на громкое вступление, если вы имели дело с микроконтроллерами, то скорее всего ничего нового в общем то здесь не узнаете 🙂 Но все таки, есть некие отличия. Вспомним, что из себя представляет периферия микроконтроллера, например GPIO. Итак, есть некая задача, настроить ножку на выход и установить ее состояние 1 или 0. Как минимум, для настройки у вас будет 2 регистра, допустим DR — настройка ножки вход или выход и OR — настройка уровня на ножке. Еще могут быть всякие разные дополнительные, например включить/выключить тактирование и т.п.

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

В linux принципы настройки периферии те же самые, пишем число по адресу — ножка настроилась как выход, по другому адресу — подали логическую 1. Однако, если бы любая программа могла случайно обратиться по адресу и бы изменять настройки периферии, то могло бы случиться все что угодно, вплоть до паленого железа. Поэтому, в операционных системах есть два уровня доступа пользовательский и уровень ядра. На пользовательском уровне вы не можете долбиться куда угодно и как угодно, зато на уровне ядра можно.

Тут стоит отвлечься на пример, самого простого модуля ядра, кстати да драйвера в linux называются модули.

#include <linux/module.h>  
#include <linux/kernel.h>   
 
int hello_init(void)
{
    printk(KERN_INFO "Hello world!\n");
    return 0;   
}
 
void hello_exit(void)
{
    printk(KERN_INFO "Exit\n");
}
 
module_init(hello_init);
module_exit(hello_exit);

Этот модуль просто выводит сообщение при загрузке и при выгрузке.

Чтобы скомпилить его понадобится создать такой makefile. Пояснять его сейчас не буду, так как это скорее всего будет в отдельной статье.

obj-m += hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

После компиляции в папке появится новый файл с расширением ko, вставляем его

sudo insmod hello.ko

И смотрим свое сообщение в логе событий ядра

dmesg

Если понадобится выгрузить модуль

sudo rmmod hello.ko

Итак, после очередного hello world вернемся к нашей проблеме. У нас есть некий GPIO, который мы хотим настроить и включить, дабы потом им управлять. Тут еще раз попытаюсь уточнить цель, предположим вы хотите написать драйвер дисплея wh1602, сама логика инициализации есть на каждом углу, ее не сложно перенести с тех же микроконтроллерных библиотек, т.е. вам остается только настроить правильно GPIO. Если вы вкурите как это делается для одной ноги, то вам ничего не стоит сделать тоже самое для любого другого нужного количества ножек. Поэтому вдупляем, настройка периферии это регистр, регистр это адрес, по адресу долбиться можно напрямую из под ядра.

То что написано ниже можно прочувствовать только плотно работая с дэйтащитом на камень. Открываем на первую малинку и рвем волосы там, где остались
arm_vaddress

В общем он взрывает мозг, тут указано 3 адреса для периферии и в какой долбиться не понятно, но если почитать инфу то становится понятно, что наружу торчит столбец с виртуальной адресацией. Не понятно, почему зачем в разделе GPIO не указаны виртуальные адреса, а указаны которые с шины CPU, т.е. 0x7e, но остальное из того же раздела вполне понятно. Итак, есть регистр GPFSEL, который указывает на то в каком режиме должна быть нога. 0 — вход, 1 — выход. Одни регистр отвечает за настройку 9 ног, пин с 0 по 9 настраивается в GPFSEL0, с 10 по 19 пины в GPFSEL1. Я использовал для тестов 17 ногу, поэтому мой пин относится к GPFSEL1. Его смещение 0x04.

Далее, если поискать есть еще 2 регистра GPSET0 и GPCLR0, один для установки логической 1, другой для логического 0. Каждый регистр используется для контроля с 0 по 31 ножку. Итак, складываем вместе всю картину, есть 3 адреса, в которые мы должны записать соответствующие числа, которые настроят ножку и установят нужное состояние. Все смещения вычисляются относительно базы указанной для GPIO. При загрузке модуля 17 нога включится, при выгрузке выключится.

#include <linux/module.h>
#include <linux/kernel.h>
 
#define PERI_BASE     0xF2000000
#define GPIO_BASE    (PERI_BASE + 0x200000)
#define GPFSEL1       0x04
#define GPSET0        0x1c
#define GPCLR0        0x28
 
volatile int *gpio_set;
volatile int *gpio_reset;
volatile int *fsel;
 
int hello_init(void)
{
   gpio_set = (int *)(GPIO_BASE + GPSET0);
   fsel = (int *)(GPIO_BASE + GPFSEL1);
 
   *gpio_set = 0x00020000;
   *fsel = 0x00200000;
 
  pr_alert("Hello world\n");
  return 0;
}
 
void hello_exit(void)
{
  gpio_reset= (int *)(GPIO_BASE + GPCLR0);
  *gpio_reset= 0x00020000;;
  pr_alert("Goodbye\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");

Чувствую, что сейчас любой человек писавший модули плачет кровавыми слезами и схватился за сердце 🙂 Дело в том, что подобный подход применим для микроконтроллеров, все что я хотел показать, что в принципе существует такая возможность. Почему же это плохо? Дело в том, что при подобном подходе, если кто нибудь захочет использовать ту же ножку, которую вы уже задействовали из под другого модуля или из пользовательского пространства, то он сможет это сделать. Чтобы не было такой возможности, используется стандартное API, которое позволяет захватить ножку и дать знать системе, что нога уже используется.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
 
int hello_init(void)
{
  gpio_direction_output(17,1);
  gpio_set_value(17, 1);
  pr_alert("Hello world\n");
  return 0;
}
 
void hello_exit(void)
{
  gpio_set_value(17, 0);
  gpio_free(17);
  pr_alert("Goodbye\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");

Как видим, в целом все тоже самое, только без напряга и правильно. Не забываем добавлять MODULE_LICENSE(«GPL»); иначе вы не сможете использовать функции, которые распространяются по этой лицензии, т.е. либа не будет компилиться.

9 комментариев: Модуль ядра для Raspberry Pi

  • Здравствуйте!
    Я так понимаю, что сначала надо установить исходники ядра.
    Случайно нет описания этой процедуры.
    Я кое-как собрал, До этого build в lib/modules не находился, потом syscall.tbl, тоже скопировал из папок с исходниками ядра, теперь tools/be_byteshift.h, который я вообще нигде не найду.
    Поэтому и хотелось бы полной процедуры подготовки с сборке данных модулей.

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

  • Спасибо, с этим разобрался. Заголовочные файлы ядра были от другой версии.
    Теперь никак не разберусь, как добавить модуль в автозагрузку, чтобы при каждой загрузке системы не проделывать insmod. Везде тоже разные методы.
    Не подскажете, как лучше это проделать на Raspberry? Куда его загрузить и куда прописать?

  • Нашел, как устанавливать модули, там, оказывается не так сложно.
    Только вот до ножек порта всё нормально, а вот когда добавляю код по работе с ними, то модуль нормально компилируется, устанавливается, только ножка порта не изменяет своего состояния, а также в dmesg получаю вот это.
    [ 989.931961] Unable to handle kernel paging request at virtual address f220001c
    [ 989.931985] pgd = 90cc0000
    [ 989.931996] [f220001c] *pgd=00000000
    Собирал и на 4 малинке и на 3-й, то же самое.
    Может сконфигурировать как-то доступ к портам надо?

  • Возможен вариант, что ножка уже занята кем то, также нужно посмотреть как вы это делаете. Правильный вариант, как в последнем примере в статье.

  • Также, как и у Вас во втором. Последний работает, только хотелось бы ещё доступ по регистрам в физической памяти, как у Вас. Только, видимо, в последних версиях Raspbian доступ стал не таким простым.

  • Не, доступ на уровне ядра должен быть одинаковый, а вот расположение адресов регистров, вероятно поменялось. Если существует дока в открытом доступе, то надо ее брать и смотреть где находятся gpio и как их конфигурить.

  • Здравствуйте!
    Хотел узнать, если это драйвер, то значит должен быть какой-то интерфейс взаимодействия с ним из пользовательского уровня? Когда разбирался с i2c взаимодействие происходило через файл в директории /dev/. Как можно осуществлять взаимодействие с этим драйвером/модулем? Или я что-то упустил?

  • Да, все так. Есть несколько методов, самый простой это char device. Глобально выглядит так: вызываете функцию, которая добавит в список /dev/ ваше устройство. Далее методами read write читаете и пишите из/в него в пользовательском пространстве. Внутри драйвера нужно будет определить такие же методы read write, внутри которых можете делать все что вашей душе угодно.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика