Open source: разработчик видеокарт раскрыл документацию для драйверов под Linux
Этот разработчик — Nvidia. Рассказываем, что было передано в open source, и обсуждаем мнения.
Что за документация
В каком-то смысле событие можно назвать знаковым. Это первый раз, когда Nvidia выкладывает документацию в большом объеме. Компания публикует спецификации с 2012 года, но до сегодняшнего дня открывали только относительно небольшие их фрагменты. Например, несколько лет назад инженеры предоставили доступ к техническим документам по графическому ядру ARM-платформы Tegra, схеме размещения Device Control Block (DCB) в VBIOS с описанием топологии платы и коннекторов и списку заголовков для GV100 Volta.
Новые спецификации выложили под лицензией MIT — найти их можно в официальном репозитории на GitHub (то же самое, но в HTML). Там есть информация о графических картах Maxwell, Pascal, Volta и Kepler: сведения о таблицах BIOS, управлении частотами, инициализации, режимах энергопотребления, обеспечении защиты в движке Falcon и заголовочных файлах для шейдеров.
По словам представителей Nvidia, новая документация тоже не полная, но репозиторий будет регулярно обновляться. Например, в будущем туда планируют добавить данные по Turing.
Мнения сообщества
Автор блога Tom’s Hardware отмечает, что развитие проектов вроде nouveau, которые создают свободные драйверы для видеокарт Nvidia, тормозило как раз отсутствие документации. Большое количество спецификаций упростит написание драйверов под Linux, и для этого не придется производить реверс-инжиниринг. Можно ожидать, что в ближайшее время практически все актуальные видеокарты начиная с 2012 года смогут получить улучшенные драйверы.
Этот факт положительно отразится на игровой индустрии, которая начинает проявлять больший интерес к открытой операционной системе. Например, буквально в конце июля Valve (разработчики магазина Steam) внесли ряд изменений в проект Proton для запуска Windows-игр под Linux. Изменения связаны с оптимизацией работы многопоточных игр.

Фото — CMDR Shane — Unsplash
Здесь стоит отметить, что в Nvidia пока не раскрыли данные, которые помогли бы реализовать поддержку реклокинга (reclocking) — автоматического управления частотой — в картах серии GTX 9xx. Этот факт не позволит новым открытым драйверам работать «на полную мощность».
Из-за отсутствия реклокинга, драйверы nouveau могут работать на 20–30% медленнее проприетарных. Хотя в будущем ситуация может измениться, когда Nvidia предоставит больше информации. Например, для архитектуры Maxwell 2 (представленной в 2014 году) патчи с функцией reclocking вышли в 2017 году.
Один из резидентов Hacker News высказал мнение, что открытые драйверы в любом случае будут обладать меньшей производительностью, чем проприетарные. В Nvidia регулярно оптимизируют драйверы под отдельные игры — для этого они плотно взаимодействуют с их разработчиками. Такой возможности у программистов из проекта nouveau может не быть.
Также в ИТ-сообществе есть мнение, что Nvidia сделали спецификацию открытой не только из-за любви к open source. В начале августа AMD реализовали поддержку своего графического процессора Navi 12 для Linux. Возможно, действия Nvidia — это попытка не отстать от конкурентов и не потерять влияние на активно развивающемся рынке.
О других графических open source решениях
В прошлом году в Nvidia представили открытую платформу Rapids. Её цель — упростить разработку алгоритмов МО на GPU. Специалисты по обработке данных пишут код на Python, а система автоматически оптимизирует его для запуска на графической карте с архитектурой CUDA.
Тогда в поддержку нового решения высказались представители таких крупных компаний, как Cisco, Dell, NetApp и Lenovo. В IBM даже провели первые тесты и отметили, что платформа позволит ускорить работу машинных алгоритмов в 50 раз (по сравнению с CPU). Но Rapids и покритиковали — за то, что система работает только с картами от Nvidia.
Свою платформу для HPC на графических картах разрабатывают и в AMD — она называется ROCm (Radeon Open Compute). В отличие от конкурентов, эта система умеет работать практически со всеми GPU. Также разработчики ROCm выбрали C++ вместо Python.
Можно предположить, что эти решения, вкупе с новыми драйверами под Linux (которые непременно появятся), позволят извлечь еще большую производительность из имеющегося в распоряжении пользователей железа.
О чем мы пишем в наших блогах и социальных сетях:
Новые лицензии для открытого ПО, кто ими занимается
Бенчмарки для Linux-серверов: 5 открытых инструментов
Как обезопасить Linux-систему: 10 советов
Big Data: большие возможности или большой обман
7 сервисов для создания видео и анимации
Как работает техподдержка 1cloud
Мы в 1cloud.ru предлагаем услугу «SSL-сертификаты». Если нужно подтвердить владение доменом или защитить свое авторское право на программу.
Пишем свой драйвер под Linux
Хочу признаться сразу, что я вас отчасти обманул, ибо драйвер, если верить википедии — это компьютерная программа, с помощью которой другая программа (обычно операционная система) получает доступ к аппаратному обеспечению некоторого устройства. А сегодня мы создадим некую заготовку для драйвера, т.к. на самом деле ни с каким железом мы работать не будем. Эту полезную функциональность вы сможете добавить сами, если пожелаете.
То, что мы сегодня создадим, корректнее будет назвать LKM (Linux Kernel Module или загрузочный модуль ядра). Стоит сказать, что драйвер – это одна из разновидностей LKM.
Писать модуль мы будем под ядра линейки 2.6. LKM для 2.6 отличается от 2.4. Я не буду останавливаться на различиях, ибо это не входит в рамки поста.
Мы создадим символьное устройство /dev/test, которое будет обрабатываться нашим модулем. Хочу сразу оговориться, что размещать символьное устройство не обязательно в каталоге /dev, просто это является частью «древнего магического ритуала».
Немного теории
Если кратко, то LKM – это объект, который содержит код для расширения возможностей уже запущенного ядра Linux. Т.е. работает он в пространстве ядра, а не пользователя. Так что не стоит экспериментировать на рабочем сервере. В случае ошибки, закравшейся в модуль, получите kernel panic. Будем считать, что я вас предупредил.
Модуль ядра должен иметь как минимум 2 функции: функцию инициализации и функцию выхода. Первая вызывается во время загрузки модуля в пространство ядра, а вторая, соответственно, при выгрузке его. Эти функции задаются с помощью макроопределений: module_init и module_exit.
Стоит сказать несколько слов о функции printk(). Основное назначение этой функции — реализация механизма регистрации событий и предупреждений. Иными словами эта функция для записи в лог ядра некой информации.
Т.к. драйвер работает в пространстве ядра, то он отграничен от адресного пространства пользователя. А нам хотелось бы иметь возможность вернуть некий результат. Для этого используется функция put_user(). Она как раз и занимается тем, что перекидывает данные из пространства ядра в пользовательское.
Хочу ещё сказать пару слов о символьных устройствах.
Между словом «disk» и датой есть два числа разделённых запятой. Первое число называют старшим номером устройства. Старший номер указывает на то, какой драйвер используется для обслуживания данного устройства. Каждый драйвер имеет свой уникальный старший номер.
Я не буду сильно углубляться в теорию, т.к. кому интересно – тот сможет сам почитать про это подробнее. Я дам ссылку в конце.
Прежде чем начать
Для компиляции модуля нам потребуются заголовки текущего ядра.
В debian/ubutnu их можно легко поставить так (к примеру для 2.6.26-2-686):
apt-get install linux-headers-2.6.26-2-686
Либо собрать пакет для вашего текущего ядра самим: fakeroot make-kpkg kernel_headers
Исходник
// Ниже мы задаём информацию о модуле, которую можно будет увидеть с помощью Modinfo
MODULE_LICENSE( «GPL» );
MODULE_AUTHOR( «Alex Petrov
» );
MODULE_DESCRIPTION( «My nice module» );
MODULE_SUPPORTED_DEVICE( «test» ); /* /dev/testdevice */
#define SUCCESS 0
#define DEVICE_NAME «test» /* Имя нашего устройства */
// Поддерживаемые нашим устройством операции
static int device_open( struct inode *, struct file * );
static int device_release( struct inode *, struct file * );
static ssize_t device_read( struct file *, char *, size_t, loff_t * );
static ssize_t device_write( struct file *, const char *, size_t, loff_t * );
// Прописываем обработчики операций на устройством
static struct file_operations fops =
<
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
>;
// Функция загрузки модуля. Входная точка. Можем считать что это наш main()
static int __init test_init( void )
<
printk( KERN_ALERT «TEST driver loaded!\n» );
// Регистрируем устройсво и получаем старший номер устройства
major_number = register_chrdev( 0, DEVICE_NAME, &fops );
// Сообщаем присвоенный нам старший номер устройства
printk( «Test module is loaded!\n» );
// Функция выгрузки модуля
static void __exit test_exit( void )
<
// Освобождаем устройство
unregister_chrdev( major_number, DEVICE_NAME );
printk( KERN_ALERT «Test module is unloaded!\n» );
>
// Указываем наши функции загрузки и выгрузки
module_init( test_init );
module_exit( test_exit );
static int device_open( struct inode *inode, struct file *file )
<
text_ptr = text;
static int device_release( struct inode *inode, struct file *file )
<
is_device_open—;
return SUCCESS;
>
static ssize_t device_read( struct file *filp, /* include/linux/fs.h */
char *buffer, /* buffer */
size_t length, /* buffer length */
loff_t * offset )
<
int byte_read = 0;
if ( *text_ptr == 0 )
return 0;
Сборка модуля
Ну а теперь можем написать небольшой Makefile:
И проверить его работоспособность:
Посмотрим что у нас получилось:
Теперь посмотрим информацию о только что скомпилированном модуле:
root@joker:/tmp/test# modinfo test.ko
filename: test.ko
description: My nice module
author: Alex Petrov
license: GPL
depends:
vermagic: 2.6.26-2-openvz-amd64 SMP mod_unload modversions
Ну и наконец установим модуль в ядро:
root@joker:/tmp/test# insmod test.ko
Посмотрим есть ли наш модуль с списке:
root@joker:/tmp/test# lsmod | grep test
И что попало в логи:
root@joker:/tmp/test# dmesg | tail
[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with ‘mknod /dev/test c 249 0’.
Наш модуль подсказываем нам что нужно сделать.
Последуем его совету:
root@joker:/tmp/test# mknod /dev/test c 249 0
Ну и наконец проверим работает ли наш модуль:
root@joker:/tmp/test# cat /dev/test
Наш модуль не поддерживает приём данных со стороны пользователя:
root@joker:/tmp/test# echo 1 > /dev/test
bash: echo: ошибка записи: Недопустимый аргумент
Посмотрим что что скажет модуль на наши действия:
root@joker:/tmp/test# dmesg | tail
[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with ‘mknod /dev/test c 249 0’.
[829747.462715] Sorry, this operation isn’t supported.
root@joker:/tmp/test# rmmod test
И посмотрим что он нам скажет на прощание:
root@joker:/tmp/test# dmesg | tail
[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with ‘mknod /dev/test c 249 0’.
[829747.462715] Sorry, this operation isn’t supported.
[829893.681197] Test module is unloaded!
Удалим файл устройства, что бы он нас не смущал:
root@joker:/tmp/test# rm /dev/test
Заключение
Дальнейшее развитие этой «заготовки» зависит только от вас. Можно превратить её в настоящий драйвер, который будет предоставлять интерфейс к вашему девайсу, либо использовать для дальнейшего изучения ядра Linux.
Только что в голову пришла совершенно безумная идея сделать sudo через файл устройства. Т.е. посылаем в /dev/test команду и она выполняется от имени root.
Литература
И под конец дам ссылку на книгу заклинаний LKMPG (Linux Kernel Module Programming Guide)
UPD2:
Поправил ошибки в исходнике.
Парсер глючит и сохраняет ‘MODULE_DEscriptION( «My nice module» );’. Естественно в module_description все буквы заглавные.
UPD3:
segoon прислал несколько поправок к посту:
1) В функции device_open() находится race condition:
static int device_open( struct inode *inode, struct file *file )
<
text_ptr = text;
Драйвера для Linux.
Где не стоит искать и брать драйвера для Linux.
Внимание: берите пакеты только с сайта разработчика ( если это Open Source) или с сайта производителя устройства. Ни каких файлообменников, особенно платных.
Было бы неплохо при покупке устройства проверить его по списку совместимых устройств который есть для Linux, также как и для Windows. Но я так ни когда не делал, всегда надеялся на русский авось. И в общем все устройства работали. Правда вполне вероятно, что какая нибудь программная кнопочка на вашем сканере, для молниеносной отправки по электронной почте отсканированного изображения, не будет работать. Но кто пользуется этими кнопочками?
Драйвера для Linux есть, но их надо првильно искать!
Пример вывода команды lsusb:
В выводе этих команд ищем строку соответствующую нашему устройству. Как правило это не составляет труда. Можно сориентироваться по названию устройства или названию чипа. Или по тому какая строка появляется при подключении USB устройства. Или методом исключения. Вот эту строку и нужно вводить в поисковую систему, Google или Yandex. Google предложит вам англоязычные сайты, и если вы способный понимать, что там написано, Google конечно предпочтительнее. Иначе используйте Yandex. Например для поиска информации по Wi-Fi адаптеру из примера нужно ввести в поиск: ID 0bda:8198 Realtek Semiconductor Corp. RTL8187B Wireless Adapter. Если по все строке не чего не нашлось, то нужно попробовать поискать по идентификатору устройства. Наша цель поиска в поиске источника драйвера и краткого алгоритма его установки. Но главное конечно сам Linux драйвер.
Обзор способов установки драйверов для Linux.
Установка драйвера для Linux из исходного кода.
Некоторые тонкости при установке драйверов для Linux.
Установка драйверов для видеокарт под Linux.
Пройдёмся по основным поколениям карт ATI:
Открытые драйвера имеют меньше проблем, поэтому рекомендую их. В этом случае для r6xx-8xx необходимо установить пакет radeon-ucode с прошивками. В первоисточнике написано: «Если не вкомпилировать их в ядро, то DRM в ядре нужно собирать модулями.» Как вкомпилировать прошивки в ядро я не представляю.
Нет драйверов на Linux, но есть на Windows. Что делать?
Средний 1 комментарий
можно на выбранный мной комп поставить Ubuntu и прикрутить туда Виндовсные драйверы?
Поставь последнюю Ubuntu, должно заработать.
Ноутов полно. Я хожу в магазины с загрузочной флешкой.
можно на выбранный мной комп поставить Ubuntu и прикрутить туда Виндовсные драйверы
еще какие-нить варианты ноутов до 800$. веб разработчика под Linux
можно на выбранный мной комп поставить Ubuntu
и прикрутить туда Виндовсные драйверы
Или как быть в такой ситуации?
Создание собственных драйверов под Linux
Многие мои друзья и знакомые крутят пальцем у виска или задаются вопросом: не жмёт ли мне череп, когда узнают, что я пишу драйвера под Linux. Слово “драйвер” окутано каким-то почти мистическим смыслом, и постичь Дао его написания способны лишь избранные гуру.
К счастью это не так. Не знаю, как обстоят дела с написанием драйверов под другие операционные системы, в т.ч. и наиболее популярные, но под linux, вне зависимости от аппаратной архитектуры драйвера пишутся очень просто. Для написания драйвера необходимы базовые знания языка си, представление о работе ОС линукс (базовые), понимание того, что мы хотим получить, желание чтения документации и исходных кодов, ну и усидчивость. Всё.
Вы хотите посмотреть как написать драйвер для своего устройства? Тогда ныряйте под кат!
Лёгкость написания драйверов объясняется тем, что исходный код linux великолепно задокументирован и доступен в исходниках, в сети множество примеров и куча литературы. А для создания драйвера вам нужен только любимый дистрибутив и исходники ядра, ну и компилятор под нужную вам архитектуру. Свой первый, не учебный, драйвер я писал для процессора PowerPC 8360, он взаимодействовал с микросхемой ПЛИС и осуществлял сброс настроек по умолчанию. Как это ни странно звучит, я его сделал, немного переписав драйвер SPI для этой архитектуры. Я это говорю к тому, что единая стандартизация написания программ в ядре Linux позволяет проводить такие штуки.
Настольная книга разработчика драйверов под Linux — это “Linux Device Drivers”. Книжка является исчерпывающим руководством по разработке и если что-то не хватает в книге, то это точно можно найти самостоятельно в исходниках ядра. Разумеется, данный пост не претендует на то, чтобы заменить эту замечательную книгу. Более того, написан он по мотивам прочтения оных трудов, но всё же не является кратким пересказом данной книги. Автор этих строк ставит собой цель заинтересовать читателя простотой и изяществом ОС Linux, и понизить порог вхождения людей в разработку драйверов. Поверьте — это очень просто!
То, для чего будем писать драйвер
Поскольку основная цель данной статьи — написание драйверов, железу мы уделим минимум внимания. Я надеюсь все дружат с паяльником, а те кто не дружат — настало время начать дружить, я кратко расскажу, что и как делалось.
Мы будем писать драйвер для дисплея семейства HD44780 подключённого через LPT-порт к компьютеру. Знаю, что порт древний, а типу дисплеев лет не меньше, чем мне, но всё же — это красивый и простой пример написания драйверов под linux, не углубляясь в аппаратные подробности. И тем более, что переделать этот драйвер для этого экрана для других архитектур будет делом замены одной строчки!
Итак, нам понадобится LPT-порт; шнурок для старого принтера и опционально разъём CENTRONICS на 36 контактов, хотя можно просто распатронить кабель; разумеется экран семейства HD44780, самое лучшее — это минимум 4х40 символов, хотя подойдут даже самые маленькие; ну и переменный резистор на 10 кОм, для регулировки контраста. Поскольку LPT-порта у меня нет уже давным-давно, я прикупил первую попавшуюся плату на PCI (можно и PCI-E). Причём плата попалась на столько китайская, что я даже не смог найти никакой документации по напаянному чипу на ней. Ничего, будем ещё и реверсить её :)))).

Аппаратные исходники
Будем считать, что достать эти компоненты не составило труда, и ваши руки горят от нетерпения всё сделать.
Предупреждение.
Внимательно прочитать!
Хочу обозначить, что мы работаем в самых тяжёлых условиях, с железом, да ещё и будем работать на уровне ядра. Живём, так сказать, внутри пациента. Любая ошибка в программе и ядро падает. При чём оно может даже не успеть помахать вам kernel_panic-ом, просто внезапно всё перестаёт работать. Обратите внимание, что вы запустили драйвер, выгрузили его, и вдруг начались неявные глюки — всё, ваш путь — перезагрузка. Если система жива, то командой reboot. Если уже нет, то аппаратный reset. Хотя при тяжёлых случаях иногда система может сама себя перезагрузить.
Плюс, если вы не знаете что делаете (пишете в порт, обращаетесь к областям памяти и т.п.), то лучше этого не делать. Ибо всё это в лучшем случае может сбить настройки (записанное неверное значение в порт 70h раньше сбрасывало BIOS в х86) или даже попортить оборудование (например посадив ножку GPIO на землю). Поэтому чётко понимайте что вы делаете, как на аппаратном, так на программном уровне!
Приступаем
Для счастливых обладателей встроенного lpt-порта в своём компьютере скажу, что в вашем случае порт живёт по стандартному адресу 378h. Меня можно среди ночи разбудить и я скажу эту цифру, столько с ней связанно. Но ныне нам дали вражескую плату, и нам предстоит её победить!
Вставляем плату в слот PCI или PCI-E, загружаемся и смотрим что у нас происходит на шине PCI следующей командой:
Как видим линух нашёл что-то на шине PCI, и даже попытался дать ему какой-то левый, явно неправильный драйвер. Но нас более всего интересует адреса, на которых сидит данный порт. Это строки: I/O ports, а именно цифры c030h и c020h.
Нам теперь нужно найти какие же адреса за что отвечают. Для этого к порту вывода подключим светодиоды с резисторами примерно по такой схеме (резисторы примерно 310 Ом, или больше или меньше, не важно)
Схема проверки
Поскольку у меня уже есть заготовка светодиодов для теста такого рода, то я вставлял провода прямо в разъем CENTRONICS и у меня не влезло все 8 штук проводов, поэтому вставил только 6 (первые 4 и последние два).

Собранный тестер порта
И набросаем небольшую программку для проверки.
Для работы с портом я взял пример отсюда www.faqs.org/docs/Linux-mini/IO-Port-Programming.html#s9. Единственное, что надо исправить строку:
Делаем нормальные задержки в секунду (вместо usleep(100000);
, пишем sleep(1);) и делаем последовательно три вывода в порт:
Дефайн #define BASEPORT меняем на порт 0xc030. Компилируем, запускаем из под рута: светодиоды не горят. Меняем на другую цифру — 0xc020 — але оп:

Нуль

На выводах AAh

И, разумеется через секунду FFh
Результат налицо. Т.е. с нумерацией портов мы угадали, можно приступать к дровам. LPT-порт данной платы живёт по адресу. 0Xc020!
Хочу обратить внимание программистов-жестянщиков на магическое число 0xAA — это последовательность единиц и нулей. Вот так: 10101010b. Это очень удобно для всяких отладок. А вообще всякий кто работает с железом должен легко в уме переводить двоичное, десятичное, шестнадцатеричное туда-сюда-обратно.
Таки дисплей
Барабанная дробь, настало время собрать железку воедино! Для этого нам осталось всё спаять вместе. Всё собираем по следующей схеме.

Знаю, что схема не по ГОСТу, но это и не журнал «Радио»
Для питания экрана я распатронил провод USB, и сейчас у меня к экрану идут два провода. Есть хитрый вариант, просто до него никак не доходят руки — это пустить питание USB прямо в проводе LPT, отпаяв один из многочисленных земляных проводов. Как дойдут руки — обязательно сделаю. Допишу только эту статью :)))).
В результате, после подачи питания, мы должны получить что-то вроде этого. Подрегулируйте яркость резистором, чтобы появились две полоски. Они свидетельствуют нам о том, что дисплей не проинициализирован.

Девайс в сборе
Фух, с аппаратной частью мы закончили, пришла пора переходить к программе.
Приступаем злобным опытам с экраном в программе
В качестве готовой либы для работы с дисплеем использовал код от Arduino взятый отсюда. Я тоже не люблю эту платформу, но код годный для дисплеев, подключаемых по I2C. Если последнее слово вам ни о чём не говорит, не пугайтесь. Суть такова, что тот же самый дисплей подключён по двум проводам (как следует из названия шины), а далее там стоит регистр, который снова преобразует переданный байт в параллельную шину, прям как у нашего LPT-порта. В результате выкинув часть работы с I2C, получим годную либу.
Для чистоты переписал на сях, добавил парочку нужных функций, переименовал некоторые конфликтные, сделал ещё какие-то изменения (не помню, много воды утекло) и получил конфетку.
К слову сказать код использовался на STM32, так же с дисплеями подключающимися по I2C. А теперь мы его портируем уже на х86 да под linux. Вот она — великая сила переносимости кода! Потратив всего 10 минут, чтобы код адаптировать уже под новую платформу.
Работа с портом осуществляется в одной единственной функции — это непереносимый узел, и в зависимости от платформы способ вывода следует менять. Тут был вывод по I2C:
Обратите внимание на эту функцию. Заменив её, код можно запустить в AVR, STM32, MIPS и т. д.
Уже знакомый нам вывод в порт. Дефайн порта определяем в хедере lcd.h. Всё, теперь можно попробовать сделать вывод. Программа стала ещё проще и нагляднее.
Для вывода на экран используется функция-обёртка print_to_string
Первый параметр номер столбца (от нуля до 19), второй номер строки (от нуля до трёх), третий — указатель на символьную строку и последний — длина строки. Строка не проверяется на окончание “\0”, длину надо контролировать самостоятельно! В результате попробуем дисплейчик:
В результате на экране должно появится что-то типа этого:

Наш первый дебют!
На этом можно было бы остановиться, т.к. с данным примером можно выводить любые символы, загружать шрифты и т.п. Но, не в качестве драйвера. Хотя можно переделать в демон или как-то ещё, насколько позволит фантазия.
Поэтому, для тех кому стало скучно читать дальше, полную версию этого примера выкладываю тут.
Наша глобальная цель — драйвер устройства, поэтому к нему и приступим.
Инструментарий для сборки драйвера
Чтобы приступить к написанию драйвера, надо поставить необходимый инструментарий. Как подготовить инструментарий для Ubuntu и Debian хорошо рассказывается вот в этой статье blog.markloiseau.com/2012/04/hello-world-loadable-kernel-module-tutorial
Лишь кратко скажу, что если вам не нужно собирать debian-пакеты, а вы пишете модуль только для себя, то достаточно будет установить необходимые компоненты следующей командой:
Всячески рекомендую так же скачать исходные тексты своего ядра. Есть два варианта это сделать. Первый, и очевидный, но возможно слегка геморройный — это узнать версию своего ядра командой:
и скачать его с www.kernel.org. Я же лентяй лазать по ссылкам и искать версию ядра, доверю это дело автоматике.
Усё, запомните этот путь или добавьте ссылку на него, например /usr/src/linux, но не рекомендую это делать, т.к. ядра имеют тенденцию к обновлению. Тут вопрос спорный быть или не быть, решайте сами.
Теперь вы имеете у себя настоящее сокровище: кладезь актуальный документации и чудовищное количество примеров исходного кода и разумеется необходимый плацдарм для сборки модуля.
Настоятельнейше рекомендую лазать в папочку Documents, drivers и прочие, просматривать исходные тексты — это невероятно полезно!
Собстна драйвер
Если вы думаете, что мы сейчас просто поправим мейкфайл и получим готовый драйвер, то вы глубоко заблуждаетесь. Всё, теперь мы находимся в другом мире: не в пространстве пользователя, в пространстве ядра. И тут действуют совсем другие правила.
Чтобы осознать всю бедовость ситуации, то вы должны понимать, что ядро — это одна большая-пребольшая программа. А это означает, что глобальные переменные, функции и т.п. могут быть доступны в других местах ядра! Поэтому ВСЕ глобальные переменные (их следует избегать по максимуму) должны быть объявлены как static!
Мы не будем пересобирать ядро, встраивая в него драйвер, и каждый раз перезапускаться, пробуя его. Это конечно забавно, но очень долго. Вместо этого, мы сделаем модуль ядра, который мы будем загружать и выгружать прямо во время работы.
В нашем будущем модуле, по сравнении с программой для пространства пользователя, изменения будут не очень значительные. Во первых, перенесём все сишные файлы в один, чтобы не иметь проблем с компиляцией (ну или инклудим сишники в друг друга). Хедер оставляем, но чётко определяем все параметры функций (если никаких параметров, то надо прописать void). Вспоминаем, что мы в пространстве ядра, и все библиотеки у нас другие. Меняем все хидеры на ядерные:
Обратите внимание, что даже unistd.h и io.h стали ядерными.
В качестве первичной основы драйвера возьмём модуль hello world. Поправим функцию init:
Здесь всё практически без изменений. Меняется у нас функция работы с железом (самая платформозависимая), т.к. будет работать уже с ядерной функцией вывода в порт:
И, как это ни странно, функция задержек. Вообще задержки — отдельная песня, которой можно посвятить целую статью. У нас же должно быть всё быстро, и как можно меньше занимать ядро на всякую ерунду. Но всё же:
Объяснение такой махинации простое: udelay внутри ядра не может принимать значения больше тысячи. Чтобы избежать казусов, необходима такая заглушка.
После этого собираем примерно таким мейкфайлом:
И загружаем модуль в систему следующей командой:
Если мы всё сделали правильно, то нам не будет выведено ошибок, а экран верно проинициализируется и dmesg покажет последней строкой сообщение вашего модуля:
[10036.950566] Lpt module init
Выгрузить модуль можно командой
[10077.176714] Cleaning up module
Вообще, чтобы не жать постоянно dmesg существует скрипт существует решение, которое мне подсказал камрад Jtu:
Или, если вы используете дистрибутив отличный от Ubuntu, то следует исполнять это под root, и убрать sudo.
Результат на экране:

Привет вам!
Если вы всё это увидели — поздравляю, это ваш первый рабочий бесполезный модуль ядра, который выводит надпись на экран! Это конечно занимательно, но надо таки превращать наши эксперименты в нормальный драйвер, с которым можно работать. А для этого всё же придётся рассказать о матчасти, хоть немного.
Пару слов о матчасти
Как ни крути, но без знания некой матчасти далеко не уйдёшь.Есть символьные, блочные и сетевые устройства. Также у каждого устройства есть старший и младший номер устройства. Лучше, чем в книге “The Linux Kernel Module Programming Guide” сказать нельзя, по этому приведу цитату из книги в спойлере:
Старший и младший номер устройства
Давайте взглянем на некоторые файлы устройств. Ниже перечислены те из них, которые представляют первые три раздела на первичном жестком диске:
Обратили внимание на столбец с числами, разделенными запятой? Первое число называют «Старшим номером» устройства. Второе — «Младшим номером». Старший номер говорит о том, какой драйвер используется для обслуживания аппаратного обеспечения. Каждый драйвер имеет свой уникальный старший номер. Все файлы устройств с одинаковым старшим номером управляются одним и тем же драйвером. Все из выше перечисленных файлов устройств имеют старший номер, равный 3, потому что все они управляются одним и тем же драйвером.
Младший номер используется драйвером, для различения аппаратных средств, которыми он управляет. Возвращаясь к примеру выше, заметим, что хотя все три устройства обслуживаются одним и тем же драйвером, тем не менее каждое из них имеет уникальный младший номер, поэтому драйвер «видит» их как различные аппаратные устройства.
Устройства подразделяются на две большие группы — блочные и символьные. Основное различие блочных и символьных устройств состоит в том, что обмен данными с блочным устройством производится порциями байт — блоками. Они имеют внутренний буфер, благодаря чему повышается скорость обмена. В большинстве Unix-систем размер одного блока равен 1 килобайту или другому числу, являющемуся степенью числа 2. Символьные же устройства — это лишь каналы передачи информации, по которым данные следуют последовательно, байт за байтом. Большинство устройств относятся к классу символьных, поскольку они не ограничены размером блока и не нуждаются в буферизации. Если первый символ в списке, полученном командой ls-l /dev, ‘b’, тогда это блочное устройство, если ‘c’, тогда — символьное. Устройства, которые были приведены в примере выше — блочные. Ниже приводится список некоторых символьных устройств (последовательные порты):
Если вам интересно узнать, как назначаются старшие номера устройств, загляните в файл /usr/src/linux/documentation/devices.txt.
Все файлы устройств создаются в процессе установки системы с помощью утилиты mknod. Чтобы создать новое устройство, например с именем «coffee», со старшим номером 12 и младшим номером 2, нужно выполнить команду mknod /dev/coffee c 12 2. Вас никто не обязывает размещать файлы устройств в каталоге /dev, тем не менее, делается это в соответствии с принятыми соглашениями. Однако, при разработке драйвера устройства, на период отладки, размещать файл устройства в своем домашнем каталоге — наверное не такая уж и плохая идея. Единственное — не забудьте исправить место для размещения файла устройства после того, как отладка будет закончена.
Еще несколько замечаний, которые явно не касаются обсуждаемой темы, но которые мне хотелось бы сделать. Когда происходит обращение к файлу устройства, ядро использует старший номер файла, для определения драйвера, который должен обработать это обращение. Это означает, что ядро в действительности не использует и даже ничего не знает о младшем номере. Единственный, кто обеспокоен этим — это сам драйвер. Он использует младший номер, чтобы отличить разные физические устройства.
Между прочим, когда я говорю «устройства», я подразумеваю нечто более абстрактное чем, скажем, PCI плата, которую вы можете подержать в руке. Взгляните на эти два файла устройств:
К настоящему моменту вы можете сказать об этих файлах устройств, что оба они — блочные устройства, что обслуживаются одним и тем же драйвером (старший номер 2). Вы можете даже заявить, что они оба представляют ваш дисковод для гибких дисков, несмотря на то, что у вас стоит только один дисковод. Но почему два файла? А дело вот в чем, один из них представляет дисковод для дискет, емкостью 1.44 Мб. Другой — тот же самый дисковод, но для дискет емкостью 1.68 Мб, и соответствует тому, что некоторые люди называют «суперотформатированным» диском («superformatted» disk). Такие дискеты могут хранить больший объем данных, чем стандартно-отформатированная дискета. Вот тот случай, когда два файла устройства, с различным младшими номерами, фактически представляют одно и то же физическое устройство. Так что, слово «устройство», в нашем обсуждении, может означать нечто более абстрактное.
Создаём файл устройства
Мы будем использовать файл-драйвер символьного устройства. Чтобы не заниматься пересказом книги Linux Device Driver, а так же упростить себе жизнь, то я взял готовый пример регистрации символьного драйвера из этой статьи. В данной статье не занимаются поиском свободного фиксированного минора, а используется динамический минор. Для нас — это самое то.
Итак, модуль теперь будет выглядеть следующем образом: работа с экраном, которую мы реализовали выше остаётся без изменений, а вот остальную часть переписываем с нуля.
Определяем структуру file_operations
Для регистрации драйвера заводим вот такую структуру
где MISC_DYNAMIC_MINOR — макрос для динамического минора, строка «lptlcd» — это название устройства, как оно будет выглядеть в папке /dev/ (в нашем случае будет /dev/lptlcd), &lptlcd_fops — указатель на структуру file_operations.
Код регистрации драйвера тоже весьма прост
Дерегистрация устройства проста:
Ну и не забываем макросы установки колбеков, лицензии, автора и версии
Магия начинается в функции dev_read и dev_write. Начну с первой
Делает она следующее: если мы произведём чтение файла устройства, например так:
То нам на экран будет выведена строка за указателем info_str.
Код настолько очевидный, что в комментариях, надеюсь, не нуждается. Единственное, что функция copy_to_user наравне с функцией copy_from_user используется копирования данных между адресными пространствами пользователя и ядра.
Другая функции dev_write, она-то и пихает данные в экран.
На мой взгляд, не добавить и не убрать. Надеюсь тут всё понятно.
Компилируем, добавляем модуль в ядро и смотрим, что у нас появился файл:
Пробуем прочитать из него и записать что-то:

Я водитель НЛО
Всё, мы теперь имеем полностью рабочий драйвер вместе с отвечающим взаимностью устройством! Поздравляю, можно бежать в лабаз за шампанским, и потом в юзерспейсе под волшебные пузырьки писать программы, работающие с этим девайсом.
Но всё же меня лично гложут какие-то сомнения, вот что-то не так…
Хочется БОЛЬШЕГО.
Итак, сейчас мы написали вполне рабочий драйвер, который уже можно использовать в повседневной практике. Выводить на него любой текст, даже текст из файла, использовать в своих программах. Но есть ощущение незавершённости, несовершенства.
Если вы возьмёте и почитаете любую документацию на экран, например статью, которую написал DIHALT о инициализации дисплея для AVR, то будете удивлены богатством возможностей. Например:
1. Можно загрузить текст в память, а затем просто двигать видимую область, экономя такты на загрузку новых данных (как помним, у нас могучие задержки).
2. Можно выводить текст в произвольную позицию.
3. Можно очищать экран.
4. И конечно, можно загружать свои шрифты. Не стоит обольщаться по этому поводу, доступно всего 8 символов. Например, задача реализация русского меню с этим экраном без русского шрифта, с латинскими буквами и частично загруженными русскими (8 символов), превращается во вполне занимательный ребус для целого отдела (было, плавали). Так что это юзается для каких-то специфических символов.
Хороший пример применения возможности загрузки шрифтов я нашёл на кофейном автомате на работе, в котором как раз установлен аналогичный экран, только русифицированный.

Экран на кофейном автомате
Вот тут отлично видно использование символов:
— два символа на сахар, пустой кружок (не догадался снять) и полный;
— пять символов бегущей строки (в символе 5 столбцов).
Итого 2+5 — остаётся всего один запасной символ, который так же, вероятно, используется. Не очень-то разбежишься.
В общем фишек у дисплея полно, так много, что мне уже лениво читать в документации. Но, как мы видим, всего этого мы делать пока не можем. И в этот чудесный момент настало время достать мой рояль из кустов, который уже давным-давно там стоит.
Прежде, чем я приступил к реализации данной идеи (даже до того, как начал покупать детали), я погуглил, а делал ли кто-то подобное. И, о чудо(!) таковой драйвер уже существует. Его написал Michael McLellan и обитает драйвер тут.
Честно скажу, данный драйвер на меня произвёл смешанное впечатление. Скажем так, он стал ценным пособием, как НЕ надо писать драйвера — переписать его под другую схему включения будет нетривиальной задачей, поскольку идёт тупая запись байтов, а каких, куда и зачем — непонятно. Переносимость отсутствует как класс. А куча define-ов для разных ядер выносят моск. Плюс, изначально драйвер задумывался под полную схему включения (8 бит на порт данных, см. схему lcd-mod.sourceforge.net/wiring.php ), а я сразу был нацелен на 4-х битную шину. Она хоть и медленнее (примерно в два раза, т.к. байт посылается двумя посылками), но во-первых, будет меньше паять, а во-вторых — не потребуется отыскивать другие порты. Ну и в третьих, у меня уже был отличный и логичный код для 4-х битной шины. В результате я решил таки написать свой драйвер сам и с нуля, попутно рассказывая как это сделать.
Ладно, к чёрту лирику. В том драйвере есть бесценная штука — это обработка управляющих или ESC-последовательностей. Если потрудится и качнуть этот “рояль”, то можно там найти всякие ридми и хау-ту. Так же на офсайте есть фак. Чтобы вам не пришлось лазать по этим всем данным, я решил собрать всё в одну кучу и на русском языке. Во первых, пару слов об этих последовательностях, вы все уже с ними сталкивались, когда операторе printf добавляли перевод строки в виде ‘\n’ — это и есть простейшая ESC-последовательность, которая интерпретируется, как символ 0Ah в ASCII. Такая же петрушка лежит в управлении данным дисплеем.
Итак, управляющие последовательности, для нашего экрана:
Другие полезные команды, работают без префикса \033!
Идея мне показалось очень крутой, и я нагло решил позаимствовать эту функцию в нашем проекте.
К слову, все эти последовательности чудесно работают в обычной консоли, ими можно пользоваться при выводе текста.
Кто ещё не понял, что это и зачем, то более подробно у него описано тут lcd-mod.sourceforge.net/faq.php. Чтобы не мучаться, я вольно перевёл его и заботливо положил в спойлер с картинками
Q. Хорошо, я установил модуль, как теперь я могу его использовать?
A. Экран будет вести себя почти так же, как vt52-терминал ru.wikipedia.org/wiki/VT52, отличие состоит только в использовании пользовательских шрифтов. Если вы хотите просто вывести текст на экран, вам следует послать его в формате ASCII на устройство. Для начала новой строки, следует отправить символ подачи строки — ‘\r’ и символ перевода каретки ‘\n’ (см. википедию ). Например, дав команду:

Перевод каретки
на экран будет выведено “Line One” и “Line Two” на двух строках экрана. От себя замечу, что опции команды “echo” означают:
-n — не завершать строку символом перевода каретки ‘\n’
-e — включается поддержка интерпретации управляющих последовательностей
Q. Когда я вывожу новую строку на экран, курсор не перемещается в начало строки?
A. Вы должны так же послать на экран символ перевода на новую строку и перевод каретки. Например:
Q. Как я могу передвигать курсор по кругу?
A. Вы можете установить курсор где хотите с помощью специальной управляющей последовательности: ESC-Y[Y-координата+037][X-координата+037]. Например:

Hello в позиции 0:0
Курсор будет перемещён в нулевую строку, нулевой столбец и будет выведено на экран слово “Hello”;
будет выведено тоже самое, но в позицию 1, 1;

Hello в позиции 1:1 (без очистки экрана)
аналогично, но напечатано будет в позицию 2, 2

Hello в позиции 2:2 (без очистки экрана, с предыдущими сообщениями)
Число “037” может немного запутать особенно, если вы не привыкли использовать восьмиричную арифметику, в будущих версиях я думаю о том, чтобы отбросить весь протокол vt52 и заменить его в более понятную реализацию (как автор жестоко ошибается 
Q. Как я могу использовать мои собственные шрифты?
A. Контроллер экрана HD4480 поддерживат до восьми символов определённых пользователем и наш модуль ядра поддерживает это, но это может быть довольной сложной операцией. Я собираюсь написать красивую GUI делающее это для вас, когда я вернусь к ней (или когда вы сможете написать об этом мне). Готов поспорить, что это предложение прочтут сущие единицы. Трололо, проверка на внимательность.
Вы можете установить один символ, отправив в устройство следующую команду: Esc-R[позиция шрифта][8 байт, определяющих битовую маску символа]. Символ определяет только последние пять бит во всём байте, поскольку в символе используется всего 5 столбцов. Я уже говорил об этом выше. Например:
Устанавливает символпо адресу 1, представляющую собой полностью закрашенный блок. Такой же, как я выше приводил у кофейного автомата в полосе готовности.

Символ полностью закрашенного блока
Устанавливает символ по адресу 0 в виде горизонтальных полос.

Символ горизонтальных полос
Чтобы вывести все «левые» символы, надо ввести следующую команду:
В общем, как видно, требуется переписать функцию dev_write. И она теперь стала выглядеть так:
А всю грязную работу по разбору полётов на себя взяла функция handleInput. Код данной функции монструозен, поэтому я его приводить тут не буду. Оставлю вам на домашнее задание разобраться с ним и найти бекдор. Кто найдёт пасхальное яйцо в коде, отмечайтесь в комментах, тому конфетка.
Для затравки видос бекдора (он реализован в драйвере)
ASCII-ART Move! Запускается пасхалкой
Те, кто покажут мне аналогичный видос на своих экранах получат от меня подарок!
В общем, в результате мы получили кошерный экран, который можно использовать для вывода разной полезной и бесполезной информации. Вот, например на него можно выводить текущее время.

И они таки ходят!
Разумеется, написал — поделись с народом! Исходные коды доступны по ссылке (всё никак не соберусь юзать гитхаб для обмена кодом, позор).
Чего хотелось бы сделать
Просто так экранчик на столе мало понятно зачем нужен, это правда. Тем более в век планшетов и прочей техники. Когда всё можно перенаправить на ваш смартфон. Другое дело, что скоро грядёт Новый Год, и вспоминается один замечательный новогодний девайс — ёлочка на процессоре lpc2104

Embedded Artists Cristmas Tree
Не стоит гуглить этот девайс, все упоминания о нём убраны даже с официального сайта, а редкие упоминания о нём сложно найти даже на просторах интернета.
Я даже снял видео работы этой ёлочки
Как работает эмбеддеровская ёлочка
Кроме весёлого мигания огоньками у этого забавного устройства была крутая особенность — можно было вести переписку с другими владельцами таких ёлочек. Был некий сервант, куда через специальную форму можно было вбить сообщение и оно рассылалось по ёлочкам и выводилось на экран. Это было очень и очень круто и весело.
Теперь внимание: идея! Сейчас ничего не мешает заплатить на месяц 500 рублей (а если скинемся, сумма будет совсем мелкой), и арендовать сервак на виртуалке. Туда поставить программулину, которая будет рассылать сообщения. Либо jabber либо какие-то другие способы (да хоть nc и telnet всё сделать). А на компе поставить приёмник этих сообщений. И на Новый Год все мы будем иметь поздравления.
Кто готов помочь это реализовать, и кто будет делать себе такие экраны? Если нас наберётся хотя бы три человека, то будет весьма и весьма весело! Вливайтесь в тусовку разработчиков :)))). Выслушаю ЛЮБЫЕ идеи!
Буду краток. Данным постом я ставил цель не сделать пересказ книги Linux Device Drivers, а просто показать, что написание дров это тривиальная задача. Я умышленно ушёл от USB, т.к. там достаточно много теории, подводных камней и прочего геморроя. Но вы вполне можете переписать данный модуль для USB-LPT-шнурков, типа таких
Делается относительно просто: перехватывается протокол обмена с usb и реализуется в вашей программе. Поверьте — это просто. Будет интересно как — я вам расскажу!
А данный драйвер, поменяв всего ОДНУ(. ) строку вывода в порт можно переписать для применения, например на Raspberry Pi на порт GPIO. Точно, что написать я вам не скажу, но всё решается чтением даташита на используемый проц и занимает всего несколько минут.
Благодарности:
1. Хочу высказать главную благодарность своему учителю Боронину Сергею Сергеевичу sboronin за обучению программированию под linux и прекрасному курсу разработки драйверов под linux. Благодаря его курсам я весьма успешно работаю разработчиком.
2. Камраду Ariman с его замечательной серией статей про разработку дисплея для роутера (первая, вторая, третья часть (а ведь обещалась четвёртая. )). Эта серия, хоть и весьма специфическая, прекрасно иллюстрирует создание устройства и написание для него соответствующих драйверов. И я неоднократно обращался к ней, как к источнику бесценной информации, как в разработке драйверов, так и в разработке модулей для OpenWRT.
3. Разумеется автору книги “Linux Device Driver” Greg Kroah-Hartman. Эта книга просто библия, и в ядре часто можно встретить драйвера, которые базируются на его примерах. Например, в драйверах USB встретил такой:
/drivers/usb/misc/idmouse.c
…
Derived from the USB Skeleton driver 1.1,
Copyright © 2003 Greg Kroah-Hartman (greg@kroah.com)
…
И так во многих драйверах. Так, что пользуйтесь книгой — это библия.
4. Michael McLellan автору аналогичного драйвера. Как он говорит в своём драйвере:
* LCD driver for HD44780 compatible displays connected to the parallel port,
* because real men use device files.
К сожалению у меня нет его координат, кроме ссылки на linkedin. Почта из его программ уже не работает. Так, что как ему лично сказать спасибо, я не знаю… Если кто ему напишет, я буду очень признателен.
Ссылки
1. Исходники моей программы для rootfs работающая с дисплеем качнуть
2. Исходники моего модуля ядра, который мы разбирали в этом посте качнуть
3. «Рояль в кустах»
4. dmilvdv.narod.ru/translate.html Переводы статей. Настольная книга — Linux Device Drivers, Third Edition».
5. Отличная библиотека статей по программированию на русском от IBM обитает тут. Конкретно по разработке модулей ядра
6. Описание дисплея HD44780 (для AVR, но в целом оно универсально) easyelectronics.ru/avr-uchebnyj-kurs-podklyuchenie-k-avr-lcd-displeya-hd44780.html
7. Программы для дисплея (модуль ядра и спектральный анализатор для этого модуля) от Michael McLellan linux.downloadatoz.com/developer-michael-mclellan.html
P.S. Не стреляйте в пианиста, он играет как умеет. Такую громадную статью при моей врождённой неграмотности написать нормально нельзя. Обязательно присылайте мне замечания, правки, и дополнения, буду очень признателен.
P.P.S. Ничто не греет душу так, как оставленный комментарий.
P.P.P.S. В тексте тоже есть пасхалка, кто найдёт — тому конфетка ;)))
UPD Вот я создал группу, для желающих участвовать меседжере 
Для участников, желающих, прошу присоединяться :)))










