Как писать драйвера

Devices programming
  • Есть два дополнения.

    Это не обязательно, но желательно иметь функцию для получения версии API драйвера. Для этого зарезервирован ioctl.io_code=0

    вот пример из недоделанного драйвера uart.

    Code: Select all

    SRV_GETVERSION equ 0
    
    ; retval
    ;  ebx= service version
    ;  eax= error code
    ;    0= no error
    ;   -1= common error
    
    align 4
    init_uart:
               mov eax, 68
               mov ebx, 16
               mov ecx, szUart
               int 0x40
    
               mov [Uart], eax
               test eax, eax
               jz .fail
    
               push 0              ;storage for version
               mov eax, esp        ;eax= pointer to output buffer
               xor ebx, ebx
    
               push 4              ;.out_size
               push eax            ;.output
               push ebx            ;.inp_size
               push ebx            ;.input
               push SRV_GETVERSION ;.code
               push [Uart]         ;.handle
    
               mov eax, 68
               mov ebx, 17
               mov ecx, esp        ;address of IOCTL in app stack
               int 0x40
               add esp, 24         ;sizeof IOCTL
               pop ebx             ;load version
               ret
    .fail:
               or eax, -1
               ret
    
    68.16 получает логический номер драйвера и сохраняет его в переменной Uart. После чего в стеке формируется стрктура IOCTL. Чтобы избежать проблем в будущем рекомендую заполнять все поля структуры. В данном примере это происходит в обратном порядке. Последним в стек помещается логический номер драйвера после чего esp становится указателем на структуру IOCTL. После вызова стек восстанавливается командой add esp,24 а pop ebx загружает возвращённый драйвером номер версии.

    Второе замечание по кодам ошибок.
    Вызовы 68.17 возвращают в eax коды ошибок. Это 0 в случае успеха и ненулевое значение в случае неудачи, обычно -1 как признак общей ошибки, другие коды неопределены. Поэтому драйвер не должен использовать регистр eax для
    возврата значений приложению. Все данные от драйвера должны возвращаться в буфере IOCTL.output

    Кстати, есть смысл определить стандартные коды ошибок при вызовах 68.17 Например от -1 коды ошибок ядра: драйвер не установлен, неправильный логический номер драйвера и т.д. от +1 коды внутренних ошибок драйвера
  • diamond - очень захватывающая статья, в высоком и живом стиле. :) Думаю еще раз пять перечитаю, и на досуге отважусь с подобными экспериментами c колибри (0.6.5.0).
  • diamond, спасибо за интересную статью.
  • Serge
    Спасибо, будет исправлено (к понедельнику).
  • diamond
    Хорошая статья, но есть ещё замечание.
    По собственному опыту я бы переписал вызов драйвера в mainloop

    Code: Select all

               push 0              ;storage for output logsize
               mov eax, esp        ;eax= pointer to output data
    
               push 16*1024        ;logsize
               push logbuf         ;pointer to log buffer
               mov  ebx, esp       ;pointer to input data
               push 4              ;.out_size
               push eax            ;.output
               push 8              ;.inp_size
               push ebx            ;.input
               push 2              ;.code
               push [handle]       ;.handle
    
               mov eax, 68
               mov ebx, 17
               mov ecx, esp        ;address of IOCTL in app stack
               int 0x40
               add esp, 24+8       ;sizeof IOCTL+ptr_logbuf+log_size
               pop ecx             ;load output logsize
    
    Так конечно длиннее и выглядит сложнее, но есть смысл рассматривать .input как указатель на структуру содержащую входные данные а .output на структуру содержащую выходные данные. В данном случае входные данные адрес logbuf и его размер, а выходные количество записанных байт. Если передаётся и возвращается только одно двойное слово всегда хочется сделать это в полях .input и .output. Но думаю что лучше так не делать потому что надо добавить в ядро проверку адресов .input и .output и страниц памяти на присутствие/запись/право доступа. Сейчас передав неправильный указатель можно обрушить всю систему.
  • Serge
    Старые замечания учтены, статья обновлена, ссылка та же. Только у меня появился вопрос: номер версии для ioctl=0 следует возвращать в каком-то фиксированном формате или совершенно произвольном? (текущая реализации ядра это вообще игнорирует, но мало ли какие могут быть планы...) В первом случае, возможно, вместо банального dword имеет смысл стандартный формат с возможностью контроля совместимости версий?
    По поводу последнего замечания: в статье .input и .output содержат действительно указатели, только на глобальные переменные, а не стековые, как в твоём примере. Конечно, если нужно посылать запросы из разных потоков многопоточной программы, нужен стек, но подобное встречается редко. Соответственно если вставить в ядро проверку на принадлежность приложению буферов, на которые указывают .input/.output, размера .inp_size/.outp_size, то разобранный в статье пример по-прежнему будет работать. А размер передаётся в поле .outp_size (причём как in/out - на входе в драйвер он содержит размер пользовательского буфера, на выходе - размер записанных данных), а иначе зачем вообще нужно это поле?
  • diamond
    Статья конечно интересная и полезная, однако у меня возник вопрос при прочтении - "Та организация, которую некоторые считают ругательным словом, не будет иметь претензии на формат заголовка?" Может быть, использовать другой более свободный вариант?
    И еще подменять можно всю функцию целиком или подфункцию тоже можно?
  • diamond

    С этими версиями одна головная боль. Для ядра важно только старшее слово - версия драйверной модели так что младшее может хранить что угодно, например ревизию svn. Для звука там хранится текущая версия API а приложения получают диапазон версий SOUND_VERSION.

    SOUND_VERSION equ 0x01000100 ;старшее слово - минимально совместимая, младшее - текущая версия.
    version dd (4 shl 16) or (SOUND_VERSION and 0xFFFF)

    Если есть идеи по формату и контролю версий, предлагай. У меня идеи уже закончились.

    По поводу .input и .output. Глобальные или локальные конечно не важно. Если программа делает много разных вызовов размещение в стеке удобней и уменьшает размер кода. Дело в передаче параметров. Все поля ioctl для драйвера входные данные - указатель на структуру входных данных, размер структуры, указатель на структуру выходных данных, размер структуры.
    То есть поля не предназначены для возврата значения. Сейчас все вызовы обрабатываются в контексте вызвавшего потока. Указатель на ioctl передаётся непосредственно. если способ передачи изменится и структура будет копироваться код перестанет работать.
    Last edited by Serge on Wed Sep 28, 2011 4:16 pm, edited 1 time in total.
  • Mario79
    Думаю, не будет. Всё-таки базовым для MS COFF (и PE, между прочим) является COFF, а он изначально был под *nix.
    Подфункцию подменять тоже можно. Самый простой способ - перехватить всю функцию и в начале обработчика проверить, вызвана ли нужная нам подфункция и если нет, то передать управление ядерному обработчику (в коде из статьи - командой jmp [oldfn70]).
    Serge
    Ну раз поля не предназначены для возврата значения, тогда надо немного изменить код. Будет исправлено.
  • [offtop]Добавь в статью её адресс, т.к. статья обновляется - всегда можно будет легко проверить соответствие сохранённой и web версий[/offtop]
  • Да я бы не сказал, что она обновляется - вот сейчас исправил последнее замечание Serge и никаких изменений в будущем не предвидится... А дата последнего обновления всегда указана на http://diamondz.land.ru
  • Кто как и когда эти драйверы запускает?

    Я скомпилировал текст

    Code: Select all

    format MS COFF
    
    include 'proc32.inc'
    include 'main.inc'
    include 'imports.inc'
    
    DEBUG            equ 1
    
    OS_BASE          equ 0
    new_app_base     equ 0x60400000
    PROC_BASE        equ OS_BASE+0x0080000
    
    
    struc IOCTL
    {  .handle           dd ?
       .io_code          dd ?
       .input            dd ?
       .inp_size         dd ?
       .output           dd ?
       .out_size         dd ?
    }
    
    virtual at 0
      IOCTL IOCTL
    end virtual
    
    section '.flat' code readable align 16
    
    proc START stdcall, state:dword
    
            mov    esi,msgStart
            call   boot_log
            mov esi, msgStart
            call SysMsgBoardStr
    	jmp $
    endp
    
    
    version       dd 0x00030003
    
    msgStart      db 'start...',13,10,0
    
    
    Записал полученный объектник в каталог drivers , но никакого эффекта не увидел.
  • Есть 2 варианта.
    1) В нужном месте в ядре вызываешь ф-цию load_driver , которая принимает один параметр - имя драйвера без .obj (загружает /rd/1/drivers/name.obj)
    2) загружаешь драйвер программно с помощью 68.16(смотри подробное описание ф-ции).
    Для начала советовал бы 2 вариант.
  • k@sTIg@r wrote:Есть 2 варианта.
    В обоих случаях возвращается 0 в EAX.
    С другими драйверами тоже.
    Я вставлял диагностику при
    stdcall load_driver, szHwMouse
    в kernel\video\cursors.inc
    возвращается 0.
  • Who is online

    Users browsing this forum: No registered users and 1 guest