- Как написать простой драйвер устройства Linux?
- 4 ответа
- Мастер SPI, подчиненный SPI?
- Протокол SPI?
- Режим SPI
- Пример драйвера устройства SPI?
- Обзор шины SPI и разработка драйвера ведомого SPI устройства для embedded Linux (Часть вторая, практическая)
- 3. Разработка userspace протокольного SPI драйвера с использованием spidev
- 4. Разработка протокольного SPI драйвера уровня ядра
- 5. Документация
Как написать простой драйвер устройства Linux?
Мне нужно с нуля написать драйвер символьного устройства SPI Linux для omap4. Я знаю основы написания драйверов устройств. Но я не знаю, как начать писать драйвер устройства для конкретной платформы с нуля.
Я написал несколько базовых символьных драйверов и подумал, что написание драйвера устройства SPI будет похоже на него. Драйверы символов имеют структуру file_operations , которая содержит функции, реализованные в драйвере.
Теперь я просматриваю spi-omap2-mcspi.c в качестве справочного материала, чтобы получить представление о том, как начать разработку драйвера SPI с нуля.
Но я не вижу таких функций, как открытие, чтение, запись и т. Д. Не знаю, откуда запускается программа.
4 ответа
Обратите внимание, что вам не удастся просто скопировать пример кода и надеяться, что он сработает, нет. API ядра может иногда меняться, и примеры не работают. Приведенные там примеры следует рассматривать как руководство, как что-то делать. В зависимости от версии ядра, которую вы используете, вам необходимо изменить пример, чтобы он заработал.
Подумайте об использовании функций, предоставляемых платформой TI, насколько это возможно, потому что это действительно может сделать для вас много работы, например, запросить и включить необходимые тактовые частоты, шины и источники питания. Если я правильно помню, вы можете использовать эти функции для получения диапазонов адресов с отображением в память для прямого доступа к регистрам. Я должен упомянуть, что у меня плохой опыт работы с функциями, предоставляемыми TI, потому что они не освобождают / не очищают все полученные ресурсы должным образом, поэтому для некоторых ресурсов мне пришлось вызывать другие службы ядра, чтобы освободить их во время выгрузки модуля.
Изменить 1:
Я не совсем знаком с реализацией SPI для Linux, но я бы начал с рассмотрения функции omap2_mcspi_probe () в файле drivers / spi / spi-omap2-mcspi.c. Как вы можете видеть, он регистрирует свои методы в главном драйвере SPI Linux с помощью этого API: Linux / include / linux / spi / spi.h. В отличие от драйвера char, здесь основными функциями являются функции * _transfer (). Посмотрите описания структур в файле spi.h для получения дополнительной информации. Также ознакомьтесь с этим альтернативным API-интерфейсом драйвера устройства.
file_operations минимальный исполняемый пример
Этот пример не взаимодействует с каким-либо оборудованием, но он иллюстрирует более простой API ядра file_operations с debugfs.
Вам также следует написать программу на C, которая запускает эти тесты, если вам не ясно, какие системные вызовы вызываются для каждой из этих команд. (или вы также можете использовать strace и узнать :-)).
Другие file_operations немного сложнее, вот еще несколько примеров:
Начните с программных моделей упрощенного оборудования в эмуляторах
Реальная разработка аппаратного обеспечения устройства «сложна», потому что:
- вы не всегда можете легко достать данное оборудование
- аппаратные API могут быть сложными
- трудно увидеть внутреннее состояние оборудования
Эмуляторы, такие как QEMU, позволяют нам преодолеть все эти трудности, моделируя упрощенное аппаратное моделирование в программном обеспечении.
QEMU, например, имеет встроенное образовательное устройство PCI под названием edu , которое я объяснил далее по адресу: Как добавить новое устройство в исходный код QEMU? и это хороший способ начать работу с драйверами устройств. Я сделал для него простой драйвер: доступно noreferrer. здесь.
Затем вы можете поместить printf или использовать GDB в QEMU, как и в любой другой программе, и точно увидеть, что происходит.
Также существует модель OPAM SPI для конкретного варианта использования: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c
Не знаю, правильно ли я понял ваш вопрос. Как указал m-ric, есть главные драйверы и подчиненные драйверы.
Обычно главные драйверы больше привязаны к оборудованию, я имею в виду, они обычно манипулируют регистрами ввода-вывода или выполняют ввод-вывод с отображением памяти.
Для некоторых архитектур, уже поддерживаемых ядром Linux (например, omap3 и omap4), главные драйверы уже реализованы (McSPI).
Итак, я предполагаю, что вы хотите ИСПОЛЬЗОВАТЬ эти возможности SPI omap4 для реализации драйвера ведомого устройства (вашего протокола для связи с вашим внешним устройством через SPI).
Я написал следующий пример для BeagleBoard-xM (omap3). Полный код находится по адресу https://github.com/rslemos/itrigue/ blob / master / alsadriver / itrigue.c (стоит посмотреть, но в нем больше кода инициализации для ALSA, GPIO, параметров модуля). Я попытался выделить код, связанный с SPI (возможно, я что-то забыл, но в любом случае вы должны уловить идею):
Чтобы записать данные на ваше устройство:
Приведенный выше код не зависит от реализации, то есть он может использовать McSPI, побитовый GPIO или любую другую реализацию ведущего устройства SPI. Этот интерфейс описан в linux/spi/spi.h
Чтобы заставить его работать в BeagleBoard-XM, мне пришлось добавить в командную строку ядра следующее:
Таким образом, создается мастер-устройство McSPI для аппаратного обеспечения omap3 McSPI4.
Надеюсь, это поможет.
Я предполагаю, что ваш OMAP4 linux использует одно из arch/arm/boot/dts/
- главный драйвер SPI готов,
- он (скорее всего) регистрируется в ядре Linux SPI drivers/spi/spi.c ,
- он (вероятно) отлично работает на вашем OMAP4.
На самом деле вам не нужно заботиться о главном драйвере , чтобы написать свой драйвер подчиненного устройства . Как я узнаю, что spi-omap2-mcspi.c является главным драйвером? Он вызывает spi_register_master() .
Мастер SPI, подчиненный SPI?
См. Documentation/spi/spi_summary . Документ относится к драйверу контроллера (ведущему) и драйверам протокола (ведомому). Из вашего описания я понимаю, что вы хотите написать драйвер протокола / устройства .
Протокол SPI?
Чтобы понять это, вам понадобится таблица данных вашего ведомого устройства, в которой будет указано:
- режим SPI , понятный вашему устройству,
- протокол , ожидаемый на шине.
В отличие от i2c, SPI не определяет протокол или рукопожатие, производители микросхем SPI должны определять свои собственные. Так что проверьте таблицу.
Режим SPI
Опять же, проверьте техническое описание устройства SPI.
Пример драйвера устройства SPI?
Чтобы дать вам соответствующий пример, мне нужно знать ваш тип устройства SPI. Вы должны понимать, что драйвер устройства флэш-памяти SPI отличается от драйвера устройства SPI FPGA . К сожалению, существует не так много драйверов для устройств SPI. Чтобы их найти:
Источник
Обзор шины SPI и разработка драйвера ведомого SPI устройства для embedded Linux (Часть вторая, практическая)
Это вторая часть моей статьи по разработке драйверов для ведомых SPI устройств в Linux. Предыдущая часть находится здесь.
3. Разработка userspace протокольного SPI драйвера с использованием spidev
Как уже было сказано выше, для SPI устройств существует ограниченная поддержка userspace API, с поддержкой базовых полудуплексных read() и write() вызовов для доступа к ведомым SPI устройствам. Используя ioctl() вызовы, можно производить полнодуплексный обмен данными с ведомым устройством, а также изменение параметров устройства.
Есть ряд причин, когда может возникнуть желание использовать данный userspace API:
- Прототипирование в окружении не подверженном возникновению неисправимых ошибок. Невалидные указатели в пространстве пользователя обычно не могут привести к краху всей системы.
- Разработка простых протоколов, используемых для обмена данными с микроконтроллерами, работающими в режиме ведомого SPI устройства, которые необходимо часто менять.
Конечно же, существуют драйверы, которые невозможно реализовать средствами userspace API, так как им необходим доступ к другим интерфейсам ядра (например, обработчики прерываний или другие подсистемы стека драйверов), недоступным в пространстве пользователя.
Для включения поддержки spidev необходимо:
1. При настройке ядра в menuconfig активировать пункт:
2. Добавить в массив структур spi_board_info, о котором шла речь в предыдущем пункте, в файле платы:
После пересборки и загрузки нового ядра в системе появится соответствующее устройство с именем вида /dev/spidevB.C, где B — номер шины SPI, а C — номер Chip select’а. Данное устройство нельзя создавать вручную через mknod, его должны автоматически создавать такие службы как udev/mdev.
Отлично, устройство у нас есть. Осталось научиться с ним работать. Предположим мы хотим отправить байт 0x8E в устройство висящее на SPI1 с номером CS 2. Наверное, самый простой способ это сделать выглядит так:
После чего на моём тестовом устройстве можно было наблюдать такую картину:
Следует сказать пару слов о тестовом устройстве. Наверное, самое главное в том, что оно почти что бесполезно и делалось лишь для изучения работы с SPI в Linux. Оно сделано на одном сдвиговом регистре 74HC164N, а на 3-х элементах 2И-НЕ из 74HC132N сделано некоторое подобие chipselect’а, которое разрешает синхронизирующий сигнал только при низком уровне на входе
CS (сразу хочу заметить, да, я знаю о существовании 74HC595, но у меня в городе её достать не удалось). У данного устройства лишь одна функция — отображать на светодиодах последний записанный в неё байт. Так как моё устройство не является полностью «честным», при чтении из него мы будем получать не то что записали (как должно было быть), а значение сдвинутое на один бит влево.
Параметры работы с ведомым устройством можно настроить посредством ioctl() вызовов. Они позволяют изменить скорость передачи данных, размер передаваемого слова, порядок байт в передаче, и естественно режим работы SPI.
Следующие ioctl() запросы позволяют управлять параметрами ведомого устройства:
- SPI_IOC_RD_MODE, SPI_IOC_WR_MODE — в случае чтения (RD) байту на который передан указатель, производится присваивание значения текущего SPI режима. В случае записи (WR), для устройства устанавливается режим соответственно значению байта по переданному указателю. Для задания режима используются константы SPI_MODE_0… SPI_MODE_3, либо же комбинация констант SPI_CPHA (фаза синхронизации, захват по переднему фронту, если установлено) и SPI_CPOL (полярность синхронизации, сигнал синхронизации начинается с высокого уровня) через побитовое «или».
- SPI_IOC_RD_LSB_FIRST, SPI_IOC_WR_LSB_FIRST — передаёт указатель на байт, который определяет выравнивание битов при передаче SPI слов. Нулевое значение показывает что старший бит является первым (MSB-first), другие значения показывают что используется более редкий вариант, и младший бит является первым (LSB-first). В обоих случаях каждое слово будет выравнено по правому краю, так что неиспользуемые/неопределённые биты будут находится в старших разрядах. RD/WR — чтение/запись параметра, определяющего выравнивание битов в словах, соответственно.
- SPI_IOC_RD_BITS_PER_WORD, SPI_IOC_WR_BITS_PER_WORD — передаёт указатель на байт, определяющий количество бит на слово при передаче данных по SPI. Нулевое значение соответствует восьми битам. RD/WR — чтение/запись количества бит на слово соответственно.
- SPI_IOC_RD_MAX_SPEED_HZ, SPI_IOC_WR_MAX_SPEED_HZ — передаёт указатель на переменную u32, которая определяет максимальную скорость передачи данных по SPI в Гц. Как правило, контроллер не может точно установить заданную скорость.
Изменяя частоту, я узнал что моё тестовое устройство может работать на частоте не выше примерно 15 МГц, что не так уж плохо с учётом длины шлейфов около 25 см, сборке на монтажной плате и соединения контактов с помощью МГТФа.
Теперь хочу сделать ещё одно важное замечание, смена порядка следования бит опять таки поддерживается не всеми контроллерами. Для того чтобы узнать какие функции поддерживает контроллер надо смотреть битовую маску spi_master.mode_bits. Определить значение битов в маске можно из определения флагов в структуре spi_device. Я не стану здесь приводить полные описания структур spi_device и spi_master, так как они не являются критически важными для понимания в данном случае. Ссылку на документацию, в которой можно найти описания всех указанных структур, я дам в конце статьи.
Как я упомянул вначале, spidev позволяет производить полудуплексные передачи, с помощью соответсвтующей команды ioctl():
где num — количество передач в массиве структур типа spi_ioc_transfer;
tr — указатель на массив структур spi_ioc_transfer;
В случае неудачи возвращается отрицательное значение, в случае успеха — суммарное количество успешно переданных байт для всех передач.
Сама структура передачи имеет следующий вид:
tx_buf и rx_buf — хранят указатели в пространстве пользователя на буфер для передачи/приёма данных соответственно. Если tx_buf равен NULL, то будут выталкиваться нули. В случае если rx_buf установлен в NULL, то данные полученные от ведомого игнорируются.
len — длина буферов приёма и передачи (rx и tx) в байтах.
speed_hz — переопределяет скорость передачи данных для данной передачи.
bits_per_word — переопределяет количество бит на слово для данной передачи.
delay_usecs — задержка в микросекундах перед деактивацией устройства (перед вызовом cs_deactivate), после передачи последнего бита данных.
Практически все поля структуры spi_ioc_transfer соответствуют полям структуры spi_transfer. Буферы данных предварительно копируются в/из пространства ядра с помощью функций copy_from_user()/copy_to_user() в недрах драйвера spidev.
Как я уже выше говорил, не все контроллеры поддерживают возможность смены скорости и размера слова для каждой передачи в отдельности, так что лучше ставьте там нули, если хотите получить переносимый код. Именно поэтому стандартный пример для работы с spidev в полнодуплексном режиме, идущий в комплекте с документацией ядра, работать без исправлений в инициализации структуры spi_ioc_transfer не станет на чипах семейства at91.
Замечания:
- На данный момент нет возможности получить реальную скорость с которой происходит выталкивание/захват бит данных для данного устройства.
- На данный момент нельзя поменять полярность chip select’а через spidev. Каждое ведомое устройство деактивируется, когда оно не находится в стадии активного использования, позволяя другим драйверам обмениваться данными с соответствующими им устройствами.
- Существует ограничение на количество байт, передаваемых при каждом запросе ввода/вывода. Как правило, ограничение соответствует размеру одной страницы памяти. Это значение может быть изменено с помощью параметра модуля ядра.
- Так как SPI не имеет низкоуровневого способа подтверждения доставки, то не существует способа узнать о наличии каких-либо ошибок при передаче, или, например, при выборе несуществующего устройства.
Теперь я приведу пример, это упрощённый вариант программы для работы с spidev идущей в комплекте с ядром. Хотя в примере это явно не показано, но никто не запрещает использовать системные вызовы read() и write() для полудуплексного обмена данными.
Думаю здесь всё достаточно очевидно, все запросы передаваемые к устройству через ioctl() мы уже разобрали. Осталось только привести Makefile для сборки:
Единственное, понадобится укзать свой путь до кросс-компилятора в переменной CC.
4. Разработка протокольного SPI драйвера уровня ядра
Разработка модуля ядра тема гораздо более объёмная, поэтому в данном случае пойдём другим путём: я приведу вначале пример кода, потом дам краткое описание его работы, объясню как его задействовать. Описывать все подробности я не стану, иначе никакой статьи не хватит, просто укажу наиболее важные моменты, в разделе статьи о документации можно найти ссылки на всю необходимую информацию. В данном примере я покажу как сделать доступными атрибуты устройства через sysfs. Как реализовать драйвер предоставляющий доступ к устройству посредством файла устройства уже обсуждалось ранее: раз, два.
Мой драйвер будет предоставлять пользователю возможность изменения двух атрибутов:
value — в него можно записать число, которое нужно отобразить в бинарном виде с помощью светодиодов;
mode — переключатель режимов, позволяет выставить один из трёх режимов работы. Поддерживаются следующие режимы: 0 — стандартный режим отображения числа в бинарном виде, 1 — режим прогресс-бара с отображением слева-направо, 2 — режим прогресс-бара с отображением справа-налево;
В режиме прогресс-бара устройство будет отображать неразрывную линию светодиодов, показывающую какой процент значение записанное в value составляет от 256. Например, если записать в mode 1, а в value 128, то засветится 4 светодиода из 8 слева.
Если в номере режима выставить третий бит, то будет использоваться полнодуплексный режим (функция fdx_transfer()), вместо асинхронных вызовов spi_write() и spi_read(). Номера полнодуплексных режимов будут соответственно 4,5,6. Режиму номер 3 соответствует 0.
Ну а теперь сам код:
Теперь, нужно добавить наше устройство в список SPI устройств в файле платы. Для моей SK-AT91SAM9260 нужно открыть файл arch/arm/mach-at91/board-sam9260ek.c и в массив структур spi_board_info добавить оную для нашего устройства (по аналогии с spidev):
Как видно из кода выше моё устройство работает на частоте 15 Мгц, висит на SPI1 с номером CS 1. Если этого не сделать, то при загрузке модуля не будет происходить связывания драйвера с устройством.
Для сборки модуля я использую следующий Makefile:
Переменная KDIR должна указывать на ваш путь с исходными кодами ядра.
Сборка производится следующим образом:
где переменная CROSS_COMPILE указывает ваш префикс кросс-компилятора.
Теперь пересобираем ядро, перекидываем наш модуль на плату и загружаем его:
После чего в системе появятся атрибутами устройства, и в ней мы увидим следующую картину:
Теперь вернёмся непосредственно к коду. Смотреть надо начинать с конца. Макросы MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, MODULE_VERSION определяют информацию которая будет доступна с помощью команды modinfo, соответственно имя автора, описание модуля, лицензия и версия. Наиболее важное значение имеет указание лицензия, ибо при использовании не GPL лицензии вы не сможете дёргать код из модулей, использующих лицензию GPL.
Макросы module_init() и module_exit() определяют функции инициализации и выгрузки модуля соответственно. В случае если модуль собран статически, функция указанная в макросе module_exit никогда не будет вызываться.
В структуре struct spi_driver spi_led_driver устанавливаются ссылки на функции привязки драйвера к устройству (probe), функции отключения устройства(remove), также имя драйвера владелец. Также там могут устанавливаться ссылки на функции перехода в энергосберегающий режи (suspend) и выхода из него (resume). Если драйвер поддерживает несколько различных устройств, одного класса, то их идентификаторы сохраняются в поле id_table.
Регистрация SPI драйвера в системе производится с помощью функции spi_register_driver(struct spi_driver * sdrv). Именно после регистрации может быть произведено связывание устройства и драйвера. Если всё пройдёт успешно, то следующей будет вызвана функция определнная в указателе probe. Убрать регистрацию драйвера из системы можно соответственно с помощью функции spi_unregister_driver (struct spi_driver * sdrv)
Функция spi_led_probe() выполняет установку параметров контроллера для работы с устройством в структуре spi_device, определённых ранее в spi_board_info. После переопределения необходимых полей в структуре spi_device вызывается функция настройки контроллера spi_setup().
Теперь обсудим атрибуты. Про работу с атрибутами устройства через sysfs можно почитать в файле Documentation/filesystems/sysfs.txt. Макрос DEVICE_ATTR предназначен для определения структуры device_attribute. Например, такое определение
где show – указатель на функцию выполянемую при открытии файла атрибута;
store – указатель на функцию выполянемую при запси в файл атрибута;
mode – определяет права доступа к файлу атрибута;
name – имя файла атрибута.
Для примера посмотрите на строку
Она определяет аттрибут устройства с названием value, разрешённый на чтение/запись пользователем, функция-обработчик запси атрибута — spi_led_store_val, функция-обработчик чтения атрибута — spi_led_show_val. Один из указателей на функции store/show может быть равен NULL, если запись/чтение данного атрибута не предусмотрены.
Посмотрим как выглядит работа с даным атрибутом:
Помните я упомянал что моя железка при чтении сдвигает данные на один бит влево? Вот именно поэтому мы получили 64 вместо записанных 32-х. Что происходит при записи числа в файл атрибута: функция strict_strtoul() пытается преобразовать полученный строковый буфер в число, потом идёт защита от дурака – проверка того что число не превышает 255. Если всё же оно больше, то возвращаем ошибку. Для пользователя это будет выгялядеть так:
Дальше идёт проверка текущего режима работы и в зависимости от него устанавливается переменная tmp. В случае режима прогресс-бара будет получено число с «неразрывными» единичными битами, иначе в SPI просто будет выведено байт заданный пользователем без изменений. В завимости от флага fduplex_mode выбирается способ передачи: полудуплексный или полнодуплексный. В первом случае используется функция spi_write(), во втором самописная fdx_transfer().
Теперь переходим полнодуплесной передаче данных. Как видите паямть под буферы (указатели mtx, mrx) для передачи у меня выделяются с помощью функции kzmalloc. Как я уже говорил, это вызвано необходимостью расположения буферов в области памяти доступной для DMA. Теперь смотрим на саму функцию fdx_transfer(). По сути она собирает сообщение spi_message, и передаёт его с помощью функции spi_sync() Байт полученный при отправке сообщения сохраняется в глобальную переменную retval, которая и будет всегда возвращаться функцией чтения атрибута value, в случае выставленного флага fduplex_mode.
Работа с атрибутом mode заключается лишь в парсинге переданного режима, разбора его на два глобальные переменных led_mode и fduplex_mode, определяющие режим отображения и дуплекса соответственно.
Наверняка многие из вас обратили внимание на то, что не понятно что будет происходить при попытке одновременно записать файл атрибута из нескольких приложений. Ничего хорошего не произойдёт точно, здесь мы имеем явный race condition. Я постарался излишне не усложнять код, но для тех кому интересно как поступать в таких случаях рекомендую почитать Unreliable Guide To Locking
Надеюсь с остальным проблем для понимания возникнуть не должно.
5. Документация
Здесь уже обсуждался вопрос поиска документации по ядру.
Исходные коды ядра поставляются с набором документации в каталоге Documentation/. Как правило, с неё и стоит начать.
Ядро работает с собственной системой документирования исходного кода kerneldoc. С помощью неё можно сгенерировать документацию по системам ядра и API с помощью следующих команд из папки исходных текстов ядра:
Документация для последней стабильной версии ядра всегда доступна по адресу: www.kernel.org/doc
Наверное, первое что оттуда стоит прочесть это Unreliable Guide To Hacking The Linux Kernel: www.kernel.org/doc/htmldocs/kernel-hacking.html
Изучение работы c SPI в Linux следует начинать с обзорных документов в каталоге Documentation/spi, в частности spi-summary и spidev. Также там есть замечательные примеры для работы через spidev: spidev_fdx.c и spidev_test.c; надеюсь вы не забыли что для некоторых контроллеров в них может понадобится внести небольшие исправления.
Про работу с атрибутами устройства через sysfs можно почитать в файле Documentation/filesystems/sysfs.txt.
Весь API используемый для работы с SPI в ядре описан тут: SPI Linux kernel API description www.kernel.org/doc/htmldocs/device-drivers/spi.html
Для облегчения поиска чего-либо в кодах ядра стоит использовать Linux Cross Reference: lxr.linux.no или lxr.free-electrons.com
Также очень рекомендую презентации от Free Electrons: free-electrons.com/docs Там собран просто замечательный набор презентаций по Embedded Linux, кратко и ясно доносящих суть дела.
На этом всё, всем спасибо за внимание, можете ругать.
Источник