Writing drivers for KolibriOS/ru: Difference between revisions
Jump to navigation
Jump to search
(Global update of the article, part 1b) |
(Global update of the article, part 1e) |
||
(3 intermediate revisions by the same user not shown) | |||
Line 20: | Line 20: | ||
== Описание работы с драйверной подсистемой == | == Описание работы с драйверной подсистемой == | ||
Драйверная подсистема позволяет загружать драйвера в формате PE и работать с ними. Для загрузки драйверов | Драйверная подсистема позволяет ядру загружать драйвера в формате PE и работать с ними. Для загрузки драйверов предусмотрено 2 системных вызова: 68.16 и 68.21, а для управления драйвером системный вызов 68.17. Драйвер должен иметь точку входа с функцией, которая должна возвращать хандлер структуры драйвера в случае успеха, и ноль в случае, если драйвер не инициализировался. Для работы драйвер импортирует некоторые функции ядра, которые экспортируются ядром как core.dll. | ||
== Драйвер == | == Драйвер == | ||
Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в | Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в git-репозитории в папке [https://git.kolibrios.org/KolibriOS/kolibrios/src/branch/main/drivers drivers]. Ну что же, давайте посмотрим [https://git.kolibrios.org/KolibriOS/kolibrios/src/branch/main/drivers/sceletone.asm sceletone.asm]): | ||
<syntaxhighlight lang="asm"> | <syntaxhighlight lang="asm"> | ||
Line 46: | Line 47: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Ну, "[http://ru.wikipedia.org/wiki/Copyright Copyright]" - он и есть копирайт, комментарий в следующей строке извещает нас, куда же мы собственно попали, это неинтересно. Дальше мы должны известить компилятор, какой формат мы хотим получить. Драйвера должны иметь формат [https://en.wikipedia.org/wiki/Portable_Executable PE]. В общем, так должно быть для всех драйверов. Дальше идёт указание функции START как точки входа, которую вызовет ядро после загрузки драйвера. После этого объявляется секция ".flat", в которой будет располагаться код нашего драйвера, после объявления секции подключаем вспомогательные файлы. | Ну, "[http://ru.wikipedia.org/wiki/Copyright Copyright]" - он и есть копирайт, комментарий в следующей строке извещает нас, куда же мы собственно попали, это неинтересно. Дальше мы должны известить компилятор, какой формат мы хотим получить. Драйвера должны иметь формат [https://en.wikipedia.org/wiki/Portable_Executable PE]. В общем, так должно быть для всех драйверов. Дальше идёт указание функции START как точки входа, которую вызовет ядро после загрузки драйвера. После этого объявляется секция ".flat", в которой будет располагаться код нашего драйвера, после объявления секции подключаем вспомогательные файлы. [https://git.kolibrios.org/KolibriOS/kolibrios/src/branch/main/drivers/proc32.inc proc32.inc] содержит макросы для определения и вызова стандартных процедур (<tt>proc/endp</tt>, <tt>stdcall/ccall/invoke/cinvoke</tt>, <tt>local</tt> для создания локальных переменных) и находится в той же папке, что и sceletone.asm. Его можно включать или не включать, макросы оттуда можно использовать или не использовать, в этой статье они используются, чтобы не усложнять восприятие (вообще говоря, при стремлении к максимальной эффективности использование макросов может повредить, но это тема отдельных жарких споров). [https://git.kolibrios.org/KolibriOS/kolibrios/src/branch/main/drivers/peimport.inc peimport.inc] содержит объявления для всех экспортируемых функций ядра. Загляните туда, ничего сложного там нет, просто куча стереотипных конструкций. На самом деле (в смысле того, как разрешается импорт при загрузке драйвера) список всех экспортируемых функций и данных ядра находится в файле {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}} (метка <tt>kernel_export</tt>), так что если вам как программисту ядра вдруг понадобиться что-нибудь своё экспортировать, лезьте туда (ну peimport.inc отредактируйте из вежливости к другим). | ||
Внимание! Здесь появляется возможность несовместимости: если вы используете какие-нибудь функции, которых (ещё или уже) нет в ядре, на которое вы рассчитываете, ядро откажется грузить ваш драйвер (ругнувшись на доске отладки нехорошим словом на ненашем языке "unresolved" с указанием имени функции). | Внимание! Здесь появляется возможность несовместимости: если вы используете какие-нибудь функции, которых (ещё или уже) нет в ядре, на которое вы рассчитываете, ядро откажется грузить ваш драйвер (ругнувшись на доске отладки нехорошим словом на ненашем языке "unresolved" с указанием имени функции). | ||
Line 1,165: | Line 1,166: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Загрузка и установка драйвера == | |||
В приведённом выше примере загрузкой драйвера занимается управляющая программа, вызывая соответствующие системные функции ядра. Но управляющая программа нужна далеко не всем видам драйверов и во многих случаях является просто приятным бонусом к основному функционалу драйвера, например для драйверов запоминающих устройств управляющая программа не обязательна и важны лишь экспортируемые ядром функции для работы с дисковой подсистемой. | |||
По этой причине для загрузки внешних драйверов можно применить другие методы. Одним из оптимальных методов является использование программы "loaddrv", которая принимает в качестве аргумента командной строки имя драйвера без его расширения и загружает его через системную функцию 68.16. Например, при исполнении команды "loaddrv sdhci" программа загрузит драйвер "/sys/drivers/sdhci.sys". Данный способ загрузки позволяет без дополнительных изменений ядра добавить загрузку драйвера, прописав его в файл "/sys/settings/autorun.dat". Этот метод предпочтителен при добавлении новых драйверов в основной образ системы. | |||
Но существуют ситуации когда драйвер не может быть добавлен в основной образ(образ дискеты) по причине слишком большого размера файла для этого образа. В данной ситуации целесообразнее написать собственный загрузчик драйвера, который определит путь к расположению файла и сможет передать необходимую для загрузки командную строку. | |||
== Мьютексы и семафоры == | Существует также ситуация, когда драйвер необходимо загрузить во время инициализации самого ядра, например для работы USB подсистемы ядро само загружает драйвера хост-контроллеров и драйвера классов устройств. Загрузка драйверов из ядра аналогична загрузке через системную функцию 68.16. Данный метод не рекомендуется, но может быть применён в специализированных под определённое железо сборках ядра. | ||
== Краткое описание экспортируемых ядром функций == | |||
Описание подробностей применения функций ядра можно достаточно легко посмотреть на основе имеющихся драйверов, но это требует достаточно хорошего знания языка ассемблера fasm и понимания общей структуры ядра. Для более краткого описания в данной статье описаны некоторые из интерфейсов ядра, предоставляемых драйверам. Описания достаточны для написания некоторых типов драйверов. | |||
=== Мьютексы и семафоры === | |||
В большинстве случаев для обработки нескольких одновременных вызовов функций необходимы средства синхронизации и блокировки доступа к ресурсам, с которыми работает данная функция. Для обеспечения такой блокировки драйвер импортирует функции, реализующие мьютексы и семафоры | В большинстве случаев для обработки нескольких одновременных вызовов функций необходимы средства синхронизации и блокировки доступа к ресурсам, с которыми работает данная функция. Для обеспечения такой блокировки драйвер импортирует функции, реализующие мьютексы и семафоры | ||
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
Line 1,184: | Line 1,196: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Работа с подсистемой событий == | === Работа с подсистемой событий === | ||
Подсистема событий является одной из важнейших частей ядра, необходимой для реализации сложных драйверов, требующих надёжный метод коммуникации между различными потоками и функциями драйвера. Подробное описание этой подсистемы находится [http://wiki.kolibrios.org/wiki/Kernel_Event/ru в отдельной статье]. | |||
== Интерфейс работы с дисковой подсистемой == | === Интерфейс работы с дисковой подсистемой === | ||
Ядро предоставляет интерфейс для добавления, удаления и работы с логическими дисками. Данный интерфейс позволяет реализовывать драйвера различных дисковых устройств, вне зависимости от их физического интерфейса, в том числе и виртуальные. <br> | Ядро предоставляет интерфейс для добавления, удаления и работы с логическими дисками. Данный интерфейс позволяет реализовывать драйвера различных дисковых устройств, вне зависимости от их физического интерфейса, в том числе и виртуальные. <br> | ||
Для добавления нового диска используется функция DiskAdd. Описание передаваемых в неё параметров приведено ниже по тексту.<br> | Для добавления нового диска используется функция DiskAdd. Описание передаваемых в неё параметров приведено ниже по тексту.<br> | ||
Line 1,193: | Line 1,206: | ||
void* DiskAdd(DISKFUNC* functions, const char* name, uint32_t userdata, uint32_t flags); | void* DiskAdd(DISKFUNC* functions, const char* name, uint32_t userdata, uint32_t flags); | ||
void DiskDel(void* hDisk); | void DiskDel(void* hDisk); | ||
void DiskMediaChanged(void* hDisk, int newstate); | |||
; Flags for add new disk | |||
DISK_NO_INSERT_NOTIFICATION = 1 | |||
</syntaxhighlight> | |||
Некоторые диски имеют возможность изменения содержимого, например CD приводы, и по этому в ядре предусмотрена функция "DiskMediaChanged". | |||
Эта функция информирует ядро о том, что носитель был вставлен, удален или изменен. Значение "newstate" должно быть равно нулю, если в данный момент носитель не вставлен, и ненулевым в противном случае. Эта функция не должна вызываться с ненулевым значением newstate ни из одной callback функции. Эта функция не должна вызываться, если активен другой вызов этой функции.<br> | |||
<br> | |||
Если при добавлении диска был установлен флаг DISK_NO_INSERT_NOTIFICATION, то драйвер не должен вызывать функцию "DiskMediaChanged" и ядро будет проверять наличие носителя при каждой операции. Данный подход может использоваться для дисков, подключение или изъятие носителей которых не может быть определено драйвером, например данный флаг применяется в драйвере контроллера floppy дисков.<br> | |||
==== Callback функции драйвера диска ==== | |||
Как уже было описано выше, при добавлении нового диска, драйвер должен передать ядру указатель на структуру DISKFUNC, которая содержит общий размер структуры(поле strucsize) и массив указателей на функции. Если функция отсутствует, то вместо неё должен быть записан ноль. | |||
<syntaxhighlight lang="asm"> | |||
struct DISKFUNC | |||
strucsize dd ? | |||
close dd ? | |||
closemedia dd ? | |||
querymedia dd ? | |||
read dd ? | |||
write dd ? | |||
flush dd ? | |||
adjust_cache_size dd ? | |||
LoadTray dd ? | |||
ends | |||
; Error codes for callback functions. | |||
DISK_STATUS_OK = 0 ; success | |||
DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable | |||
DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters | |||
DISK_STATUS_NO_MEDIA = 2 ; no media present | |||
DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data | |||
DISK_STATUS_NO_MEMORY = 4 ; insufficient memory for driver operation | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="C"> | |||
void close(void* userdata); | |||
</syntaxhighlight> | |||
Необязательная функция. Функция которая освобождает все ресурсы, зависящие от драйвера, для диска.<br> | |||
<syntaxhighlight lang="C"> | |||
void closemedia(void* userdata); | |||
</syntaxhighlight> | |||
Необязательная функция, может отсутствовать если носитель не является съемным. Функция, вызов которой информирует драйвер о том, что ядро завершило всю обработку с текущим носителем. Если носитель удален, драйвер должен отклонять все запросы к этому носителю с помощью команды DISK_STATUS_NO_MEDIA, даже если вставлен новый носитель, до тех пор, пока не будет вызвана эта функция. Если носитель удален, новый вызов 'disk_media_changed' не разрешен до тех пор, пока не будет вызвана эта функция. <br> | |||
<syntaxhighlight lang="C"> | |||
int querymedia(void* userdata, DISKMEDIAINFO* info); | |||
; Media flags. Represent bits in DISKMEDIAINFO.Flags. | |||
DISK_MEDIA_READONLY = 1 | |||
struct DISKMEDIAINFO | |||
Flags dd ? ; Combination of DISK_MEDIA_* bits. | |||
SectorSize dd ? ; Size of the sector. | |||
Capacity dq ? ; Size of the media in sectors. | |||
LastSessionSector dd ? ; Number last session sectors for CDFS | |||
ends | |||
</syntaxhighlight> | |||
Обязательная функция, которая производит заполнение структуры DISKMEDIAINFO и возвращает DISK_STATUS_* код.<br> | |||
<syntaxhighlight lang="C"> | |||
int read(void* userdata, void* buffer, __int64 startsector, int* numsectors); | |||
</syntaxhighlight> | |||
Обязательная функция. Функция для чтения секторов диска в буфер "buffer", начиная с сектора "startsector". Количество считываемых секторов находится по указателю "numsectors". Функция должна возвращать DISK_STATUS_* код и записать количество успешно прочитанных секторов по указателю "numsectors". Указатель на буфер является виртуальным адресом. <br> | |||
<syntaxhighlight lang="C"> | |||
; int write(void* userdata, void* buffer, __int64 startsector, int* numsectors); | |||
</syntaxhighlight> | |||
Необязательная функция. Функция для записи секторов диска из буфера "buffer", начиная с сектора "startsector". Количество записываемых секторов находится по указателю "numsectors".Функция должна возвращать DISK_STATUS_* код и записать количество успешно записанных секторов по указателю "numsectors". Указатель на буфер является виртуальным адресом. <br> | |||
<syntaxhighlight lang="C"> | |||
int flush(void* userdata); | |||
</syntaxhighlight> | |||
Необязательная функция. Функция очищает внутренний кэш устройства и возвращает DISK_STATUS_* код. Обратите внимание, что функции чтения/записи вызываются менеджером кэша, поэтому драйвер не должен создавать программный кэш. Эта функция реализована для очистки аппаратного кэша, если он существует. <br> | |||
<syntaxhighlight lang="C"> | |||
unsigned int adjust_cache_size(void* userdata, unsigned int suggested_size); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Необязательная функция. Функция возвращает размер кэша для данного устройства в байтах. При возврате нуля программный кэш не используется. <br> | |||
<syntaxhighlight lang="C"> | |||
int LoadTray(void* userdata, int flags); | |||
</syntaxhighlight> | |||
Необязательная функция. Функция для загрузки/выгрузки носителя, операция определяется флагом: 0 - загрузить, 1 - выгрузить. Функция возвращает DISK_STATUS_* код.<br> | |||
== Интерфейс работы со встроенными устройствами == | === Интерфейс работы со встроенными устройствами === | ||
Встроенные устройства включают в себя всевозможные устройства, расположенные непосредственно на материнской плате или подключаемым к внутренним шинам, например ISA или PCIe. | Встроенные устройства включают в себя всевозможные устройства, расположенные непосредственно на материнской плате или подключаемым к внутренним шинам, например ISA или PCIe. | ||
=== Прерывания === | ==== Прерывания ==== | ||
Прерывания используются многими встроенными устройствами для оповещения драйверов об изменении их состояния, например, для оповещения о подключении сетевого кабеля или нажатие на клавишу клавиатуры. | Прерывания используются многими встроенными устройствами для оповещения драйверов об изменении их состояния, например, для оповещения о подключении сетевого кабеля или нажатие на клавишу клавиатуры. | ||
Для добавления своего обработчика прерывания, драйвер должен вызвать функцию в которую передаётся номер прерывания, указатель на функцию обработчика прерывания и необходимые ей данные в виде 4 байт. | Для добавления своего обработчика прерывания, драйвер должен вызвать функцию в которую передаётся номер прерывания, указатель на функцию обработчика прерывания и необходимые ей данные в виде 4 байт. | ||
Line 1,213: | Line 1,306: | ||
Если вызванный обработчик прерывания не обнаружил взаимодействия от настроенного на него контроллера, то обработчик должен вернуть 1 в качестве ответа. В иных случаях обработчик должен вернуть ноль. | Если вызванный обработчик прерывания не обнаружил взаи |