Writing drivers for KolibriOS/ru: Difference between revisions

From KolibriOS wiki
Jump to navigation Jump to search
m (the link has been fixed)
(Global update of the article, part 1d)
Line 24: Line 24:
== Драйвер ==  
== Драйвер ==  


Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в svn-репозитории вместе с ядром, точнее, в папке {{#svn:/kernel/trunk/drivers|svn://kolibrios.org/kernel/trunk/drivers}}. Ну что же, давайте посмотрим ({{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm из #450|450}}):
Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в 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 47: Line 47:
</syntaxhighlight>
</syntaxhighlight>


Ну, "[http://ru.wikipedia.org/wiki/Copyright Copyright]" - он и есть копирайт, комментарий в следующей строке извещает нас, куда же мы собственно попали, это неинтересно. Дальше мы должны известить компилятор, какой формат мы хотим получить. Драйвера должны иметь формат  [https://en.wikipedia.org/wiki/Portable_Executable PE]. В общем, так должно быть для всех драйверов. Дальше идёт указание функции START как точки входа, которую вызовет ядро после загрузки драйвера. После этого объявляется секция ".flat", в которой будет располагаться код нашего драйвера, после объявления секции подключаем вспомогательные файлы. {{#svn:/drivers/proc32.inc|proc32.inc}} содержит макросы для определения и вызова стандартных процедур (<tt>proc/endp</tt>, <tt>stdcall/ccall/invoke/cinvoke</tt>, <tt>local</tt> для создания локальных переменных) и находится в той же папке, что и {{#svn:/drivers/sceletone.asm|sceletone.asm}}. Его можно включать или не включать, макросы оттуда можно использовать или не использовать, в этой статье они используются, чтобы не усложнять восприятие (вообще говоря, при стремлении к максимальной эффективности использование макросов может повредить, но это тема отдельных жарких споров). {{#svn:/drivers/peimport.inc|peimport.inc}} содержит объявления для всех экспортируемых функций ядра. Загляните туда, ничего сложного там нет, просто куча стереотипных конструкций. На самом деле (в смысле того, как разрешается импорт при загрузке драйвера) список всех экспортируемых функций и данных ядра находится в файле {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}} (метка <tt>kernel_export</tt>), так что если вам как программисту ядра вдруг понадобиться что-нибудь своё экспортировать, лезьте туда (ну и {{#svn:/drivers/peimport.inc|peimport.inc}} отредактируйте из вежливости к другим).
Ну, "[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,195: Line 1,195:
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>
</syntaxhighlight>


TODO!!!
Некоторые диски имеют возможность изменения содержимого, например CD приводы, и по этому в ядре предусмотрена функция "DiskMediaChanged".
Тут должно быть про callback функции, флаги и 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>
Необязательная функция. Функция возвращает размер кэша для данного устройства в байтах. При возврате нуля программный кэш не используется. <br>
 
<syntaxhighlight lang="C">
int LoadTray(void* userdata, int flags);
</syntaxhighlight>
Необязательная функция. Функция для загрузки/выгрузки носителя, операция определяется флагом: 0 - загрузить, 1 - выгрузить. Функция возвращает DISK_STATUS_* код.<br>
    
    
== Интерфейс работы со встроенными устройствами ==
== Интерфейс работы со встроенными устройствами ==
Line 1,324: Line 1,404:


== Звуковая подсистема ==
== Звуковая подсистема ==
Краткое описание интерфейса находится в статье [http://wiki.kolibrios.org/wiki/Writing_sound_drivers_for_KolibriOS о драйверах звуковых карт].


== Остальные импортируемые ядром функции ==
== Остальные импортируемые ядром функции ==
Line 1,346: Line 1,428:


=== Функции таймера ===
=== Функции таймера ===
<syntaxhighlight lang="C">
void* TimerHS(unsigned int deltaStart, unsigned int interval,
              void* timerFunc, void* userData);
void CancelTimerHS(void* hTimer);
</syntaxhighlight>
TODO


=== Функции вывода в доску отладки ===
=== Функции вывода в доску отладки ===

Revision as of 12:58, 16 June 2024


ВНИМАНИЕ: Данная статья находится в разработке и содержит множество устаревших и непроверенных данных. В данный момент использование этой статьи в качестве справочного материала не рекомендуется.

ПРЕДУПРЕЖДЕНИЕ: Данная статья устарела и переписывается. НЕ ИСПОЛЬЗУЙТЕ ЭТУ СТАТЬЮ.

Вступление

Предупреждение 0. Данная статья описывает написание драйверов для основной ветки Колибри ОС и не включает в себя исторические моменты. Предыдущая версия статьи находится на форуме по ссылке.

Предупреждение 1. Прежде чем писать драйвер, хорошо подумайте, нельзя ли обойтись средствами прикладных API, в частности, функций работы с оборудованием 46 и 62. Во-первых, от ошибки в кривом приложении пострадает только это кривое приложение, а кривой драйвер способен без особого труда обрушить всю систему. Во-вторых, для приложений можно вылавливать баги в отладчике MTDBG, обладающем определёнными возможностями, а для драйверов этот путь закрыт (разве что встроенный отладчик эмулятора Bochs, но он заведомо непригоден для отладки с реальным железом), так что единственным средством остаётся отладочный вывод на доску отладки BOARD со всеми недостатками.

Далее допустим, что вы всё ещё читаете эту статью. Мало ли, может, вы всегда пишете код с первого раза безошибочно (чего только на свете не бывает), или в совершенстве владеете отладкой прямо в мозгу и считаете всякие отладочные средства баловством, или просто считаете, что настоящий мужчина (настоящая леди?) не боится трудностей и несколькими строчками текста вас не напугать.

Предупреждение 2. Драйвера, естественно, тесно связаны с ядром. А в ядро КолибриОС вносятся изменения несколько раз в неделю. Разумеется, большинство изменений никак не касается драйверной подсистемы, но иногда добавляются/исчезают/изменяются важные системные функции, экспортируемые драйверам. Поэтому если вы возьмёте и скомпилируете прилагаемый к статье код, то, возможно, он прямо в таком виде работать не будет. Так что внимательно читайте текст - я постараюсь выделить по возможности все причины неработоспособности в будущем и требуемые модификации. Прилагаемый к статье код рассчитан на ревизию {{#svn_rev:450}}, последнюю на момент написания этих строк (в дистрибутиве 0.6.5.0 работать в таком виде не будет).

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

Описание работы с драйверной подсистемой

Драйверная подсистема позволяет ядру загружать драйвера в формате PE и работать с ними. Для загрузки драйверов предусмотрено 2 системных вызова: 68.16 и 68.21, а для управления драйвером системный вызов 68.17. Драйвер должен иметь точку входа с функцией, которая должна возвращать хандлер структуры драйвера в случае успеха, и ноль в случае, если драйвер не инициализировался. Для работы драйвер импортирует некоторые функции ядра, которые экспортируются ядром как core.dll.

Драйвер

Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в git-репозитории в папке drivers. Ну что же, давайте посмотрим sceletone.asm):

<syntaxhighlight lang="asm">