Введение во встроенные системы и Windows Embedded CE

Введение в драйверы устройств ввода/вывода

Разбить на страницы
Показывать лекцию целиком

Эта телефонная система LG Electronics Voice over IP (VoIP) основывается на Windows Embedded CE. Фотография с разрешения Mike Hall.

Введение в драйверы устройств В/В

Драйверы устройств помогают создать слой абстракции между оборудованием и прикладными программами пользователя. Типичный пользователь, разрабатывающий приложение, не должен понимать низко-уровневые детали оборудования каждого интерфейса. Вместо этого прикладная программа вызывает API ОС, который вызывает драйвер устройства. Работая на более высоком уровне абстракции, используя ОС и драйверы устройств, программисты приложений могут быть более продуктивными. При использовании ОС и драйверов устройств приложения можно переносить на различные аппаратные платформы с меньшими усилиями, и система будет также более стабильной и безопасной. ОС является критически важной в планировании, размещении, и управлении устройствами, которые совместно используются несколькими процессами. В качестве примера представьте, что произойдет, если два приложения начнут одновременно посылать символьные данные непосредственно оборудованию принтера. В результате получится случайная смесь символов на каждой напечатанной странице.

Большинство распространенных драйверов устройств поставляются вместе с ОС или доступны производителям микросхем, но многие нужно будет переносить на новую аппаратную платформу, так как даже небольшие изменения, такие как задание адреса порта В/В или уровня прерывания могут требовать небольших модификаций кода драйвера.

Одной из проблем, постоянно встречающейся разработчикам встроенных систем, является необходимость разработки драйверов устройств для уникального оборудования, вводимого при проектировании каждой новой платы. Документация по интерфейсам драйверов и примеры драйверов устройств поставляются с каждой ОС. Детали реализации драйверов варьируются от ОС к ОС. Может также поставляться инструмент типа мастера, помогающий сгенерировать шаблонный код, необходимый в новом драйвере устройства для правильного взаимодействия с ОС. В некоторых случаях драйверы устройств покупаются даже у сторонних разработчиков или разрабатываются внешними консультантами. Написаны целые книги по теме написания драйверов устройств. Библиотеки драйверов устройств CE перечислены в таблице 9.1.

Библиотеки драйверов устройств CE
Библиотека Описание
Собственные библиотеки микропроцессора Драйверы устройств для тесной интеграции собственных периферийных устройств микропроцессора. Например, микропроцессор ARM, и его дополнительная микросхема интегрирует многие периферийные устройства микропроцессора, такие как LCD, последовательный порт, USB Host, функцию USB, и т.д. SDB или аппаратная платформа, которая использует специальный микропроцессор будут использовать то же самое множество собственных драйверов микропроцессора. В Windows Embedded CE большинство микропроцессоров являются высоко интегрированными микропроцессорами, которые содержат много собственной периферии. Они связаны с так называемыми драйверами SOC (система-на-микросхеме) и находятся в ..\WINCE600 \Platform\Common\Src\SOC.
Специфические для микропроцессора библиотеки поддержки OAL Функции OAL для устройств, таких как часы реального времени, таймер, и контроллер отладки Ethernet, который обычно присутствует в микропроцессоре. Эта библиотека минимизирует написанный код OAL. Они также называются драйверами SOC (система-на-микросхеме) и находятся в ..\WINCE600 \Platform\Common\Src\SOC.
BSP или драйверы специфические для аппаратной платформы Драйверы устройств для периферии, которые являются специфическими для заданного SDB или аппаратной платформы. Эти драйверы имеют специфический для аппаратной платформы код и могут использоваться только на этой аппаратной платформе. Эти драйверы находятся в ..\WINCE600\Platform\<Hardware Platform Name> \Src\Drivers.
Другие распространенные драйверы периферийных устройств Драйверы для стратегических периферийных наборов микросхем, которые обычно встречаются во многих SDB или конструкциях аппаратных платформ. Сюда входят такие устройства как Realtek RTL8139, базовый NE2000, наборы микросхем DEC/Intel 2114x Ethernet, MediaQ MQ200, наборы микросхем дисплея ATI Rage XL, и наборы микросхем аудио Ensoniq. Цель состоит в том, чтобы предоставить драйверы производственного качества для 50% стратегических наборов микросхем, которые покрывают более 75% аппаратных платформ, доступных на рынке. Они называются общими драйверами и находятся в . .\WINCE600\Public\Common\Oak\Drivers. Исходный код для модулей находится в каталоге Public.

Модель потокового интерфейса для драйверов устройств

ДрайверомPart of the material presented in this Chapter was obtained from or is derived from the CE 6.0 on-line help files, MEDC 2006 conference presentations by members of the Windows Embedded CE design team, and Microsoft’s Windows Embedded CE Professional Training Materials. Reprinted with Permission of Microsoft Corporation. с потоковым интерфейсом является любой драйвер, который предоставляет функции потокового интерфейса, независимо от типа устройства, которым управляет драйвер. Модель потокового интерфейса является простейшим способом реализации большинства драйверов пользователя, и приложения обращаются к ним с помощью обычных функций API файловой системы. Подобно многим свойствам ОС, подход на основе потокового интерфейса ведет свое начало из первых версий Unix.

Потоковый интерфейс подходит для любого устройства В/В, которое можно логически считать источником или стоком данных. То есть, любое периферийное устройство, которое создает или потребляет потоки данных как свою основную функцию, является хорошим кандидатом для предоставления потокового интерфейса. Примером является устройство потокового порта. Примером устройства, которое не создает и не потребляет данные в традиционном смысле, будет устройство дисплея, и действительно, для управления оборудованием дисплея потоковый интерфейс не предоставляется.

Потоковый интерфейс может использовать другой нижележащий драйвер устройства для доступа к физическим периферийным устройствам, которыми управляет драйвер, или он может обращаться к устройству непосредственно, если устройство отражено в память. Драйверы аудио-устройств для встроенного аудио-оборудования являются примером прямого доступа.

Сами функции потокового интерфейса создаются таким образом, чтобы как можно ближе соответствовать семантике обычных интерфейсов прикладного программирования (API) файловых систем, таких как CreateFile, WriteFile, ReadFile, IOControl, и CloseHandle. В качестве побочного эффекта такого дизайна устройства, которые управляются потоковым интерфейсом, доступны для приложений через файловую систему; приложения взаимодействуют с драйвером, открывая специальные файлы в файловой системе.

Драйверы потокового интерфейса могут иметь монолитную архитектуру или архитектуру с двумя слоями, MDD и PDD, как показано на рисунке 9.1.

(рис 9.1) Две альтернативные архитектуры драйвера потокового интерфейса. Монолитный драйвер потокового интерфейса или Разделенный на слои драйвер потокового интерфейса. В разделенной на слои архитектуре драйвера в CE два слоя называются MDD и PDD

Интерпретация устройств как специальных файлов распространена во многих операционных системах, включая настольные версии Microsoft Windows и даже первые версия Unix. Там устройства печати традиционно представлялись именами специальных файлов LPTx:, а последовательные порты именами специальных файлов COMx:, и т.д.

Несмотря на базовые характеристики потокового интерфейса, его можно реализовать различным образом. Например, даже хотя потоковый интерфейс обычно создается для периферийных устройств независимыми поставщиками оборудования (IHV), производители оригинального оборудования (OEM) могут предоставлять потоковый интерфейс для определенных встроенных устройств.

В некоторых менее распространенных случаях драйверы потокового интерфейса могут переупаковывать существующие ресурсы, обычно таким образом, который специфические приложения смогут более легко использовать. Например, такой тип драйвера потокового интерфейса может управлять приемником системы глобального позиционирования (GPS) с последовательным интерфейсом. В этом примере ISV может выбрать создание специального драйвера потокового интерфейса для работы в соединении с приложением отображения GPS. Многие приемники GPS соединяются с последовательными портами. Приложение отображения GPS может, поэтому, открыть специальный файл COMx:, соответствующий последовательному порту и непосредственно взаимодействовать с приемником GPS.

Однако приемник GPS может предоставлять данные позиционирования в неудобном формате, или создатель приложения может захотеть сохранить скрытыми детали управления специальными моделями приемников GPS. Поэтому можно написать драйвер потокового интерфейса для обеспечения взаимодействия между приложением и приемником GPS. Драйвер будет взаимодействовать с приемником GPS как и раньше через специальный файл COMx:, но может переупаковывать данные позиционирования в более удобном формате для приложения. Драйвер может предоставлять свои собственные службы как специальный файл GPSx:, который будет открывать приложение, чтобы прочитать данные позиционирования.

Драйвер потокового интерфейса получает команды от Device Manager или из приложений посредством вызовов файловой системы. Драйвер инкапсулирует всю информацию, которая необходима для трансляции этих команд в соответствующие действия на устройствах, которыми он управляет.

Все драйверы потоковых интерфейсов, управляют ли они встроенными устройствами или устанавливаемыми устройствами, загружаются ли они во время начальной загрузки или загружаются динамически, имеют похожие взаимодействия с другими системными компонентами. Рисунок 9.2 показывает архитектуру драйверов потокового интерфейса для встроенных устройств, которые загружаются Device Manager во время начальной загрузки.

Различные вопросы могут влиять на конструктивные решения во время реализации динамически подключаемой библиотеки (DLL) драйвера потокового интерфейса. Чтобы реализовать драйвер потокового интерфейса, создайте DLL, содержащую требуемые точки входа для драйверов, и затем решите, хотите ли вы реализовать одиночный или множественный доступ к драйверу.

Можно реализовать драйвер только с точками входа Init и Deinit и без префикса устройства. Невозможно получить доступ к этому драйверу с помощью CreateFile. PCIBUS и RegEnum являются двумя примерами такого типа драйвера. Эти драйверы находятся в каталогах ..\WINCE600\Public\Common\OAK\Drivers\PCIbus и ..\WINCE600\Public\Common\ OAK\Drivers\RegEnum.

(рис 9.2) Архитектура потокового интерфейса

Если DEVFLAGS_NAKEDENTRIES определен в подключе реестра Flags драйвера, то имена точек входа могут быть лишены отличий; например, Open, Close, и т. д. Пример драйвера батареи, который находится в ..\WINCE600\Public\Common\OAK\Drivers\Battdrvr, является примером драйвера, который использует точки входа без отличий. Настройки реестра драйвера батареи должны тем не менее включать префикс.

Реализации этих точек входа должны объявляться для экспорта из DLL, размещая __declspec(dllexport) перед объявлением функции. При разработке в C++, точки входа должны также объявляться как внешние "C".

Таблица 9.2 показывает функции драйвера потокового интерфейса с описанием назначения каждой из них. XXX в имени каждой функции заменяется трехсимвольным именем устройства. Эти функции определяются внутренне только для менеджера устройств, а не для приложений пользователя.

Функции драйвера потокового интерфейса.
Программный элемент Описание
XXX_Close Эта функция закрывает контекст устройства, определенный hOpenContext. Эта функция требуется для доступа к устройству с помощью CreateFile. Если вы реализуете XXX_Close, вы должны реализовать XXX_Open.
XXX_Deinit Эта функция де-инициализирует устройство. Ее вызывает Device Manager. Эта функция требуется драйверам, загружаемым ActivateDeviceEx, ActivateDevice, или RegisterDevice.
XXX_Init Эта функция инициализирует устройство. Ее вызывает Device Manager. Эта функция требуется драйверам, загружаемым ActivateDeviceEx, ActivateDevice, или RegisterDevice.
XXX_IOControl Эта функция посылает команду на устройство. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.
XXX_Open Эта функция открывает устройство для чтения, записи, или того и другого. Приложение неявно вызывает эту функцию, когда вызывает CreateFile для получения указателя на устройство. Эта функция требуется для доступа к устройству с помощью CreateFile.
XXX_PowerDown Не обязательная. Эта функция выключает питание устройства. Она будет полезна только с устройствами, которые можно выключить с помощью программного управления.
XXX_PowerUp Не обязательная. Эта функция восстанавливает питание в устройстве.
XXX_PreClose Не обязательная. Эта функция помечает закрывающий указатель как недействительный, и пробуждает все спящие потоки.
XXX_PreDeinit Эта функция помечает экземпляр устройства как недействительный и пробуждает спящие потоки. Эта функция требуется, если реализована функция XXX_PreClose.
XXX_Read Эта функция считывает данные из устройства, идентифицированного открытым контекстом. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.
XXX_Seek Эта функция перемещает указатель данных в устройстве. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.
XXX_Write Эта функция записывает данные в устройство. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.

Так как периферийные устройства доступны приложениям как файлы, когда создается потоковый интерфейс, то нужно предоставить реализацию такого файла. Поэтому, рассмотрите, будет ли это практично, на основе возможностей устройства, которые предоставляет драйвер, чтобы позволить нескольким приложениям иметь одновременный доступ к файлу, который представляет устройство. То есть, рассмотрите, может ли драйвер иметь несколько открытых указателей файлов на своем устройстве. Драйвер потокового интерфейса может реализовать одиночный доступ или множественный доступ, используя параметр hOpenContext, передаваемый всем функциям В/В файла.

Чтобы реализовать множественный доступ, каждый вызов функции XXX_Open должен возвращать различное значение для hOpenContext. Драйвер устройства должен отслеживать, какие возвращаемые значения из XXX_Open используются. Последующие обращения к XXX_Close, XXX_Read, XXX_Write, XXX_Seek, и XXX_IOControl передают эти значения назад драйверу устройства, позволяя драйверу идентифицировать, какими внутренними структурами данных манипулировать.

Для реализации одиночного доступа, только первый вызов XXX_Open должен возвращать действительное значение hOpenContext. Пока это значение остается действительным, что будет иметь место, пока для этого значения не будет вызвана функция XXX_Close, последующие обращения к XXX_Open должны возвращать NULL вызывающему приложению, чтобы указать на отказ.

Обработка ISR и IST для драйверов устройств

Большинство периферийных устройств могут генерировать прерывания, чтобы получить обслуживание от операционной системы. Некоторыми примерами устройств, которые могут использовать прерывания, являются платы ПК, встроенные таймеры, устройства аудио-ввода, клавиатуры, сенсорные экраны, последовательные порты, и устройства указатели. Почти любой тип периферийного устройства может использовать прерывания в качестве основного метода инициирования действий ОС по обслуживанию.

Так как эти периферийные устройства могут вызывать или сигнализировать о прерываниях, их драйверы устройств должны обрабатывать прерывания, чтобы обслужить свои устройства. Физические прерывания (IRQ) являются аппаратными линиями связи, по которым устройства могут посылать сигналы прерываний в микропроцессор. Логические прерывания (SYSINTR) являются отображением IRQ, которое определяет OAL.

Когда обрабатывается прерывание, имеет место определенная последовательность событий. Необходимо написать для драйвера устройства запрос на обслуживание прерывания (ISR) и поток обслуживания прерывания (IST), помня о следующей последовательности событий.

  • Когда происходит прерывание, микропроцессор переходит к обработчику исключительных ситуаций ядра.
  • Обработчик исключительных ситуаций отключает все прерывания равного или более низкого приоритета микропроцессора, и затем вызывает соответствующий ISR для запроса физического прерывания (IRQ).
  • ISR возвращает логическое прерывание в форме идентификатора прерывания обработчику прерываний и, обычно, маскирует прерывание устройства уровня платы.
  • Обработчик прерываний снова включает все прерывания в микропроцессоре, за исключением текущего прерывания, которое остается замаскированным на плате, и затем сигнализирует о соответствующем событии IST.
  • IST планируется, обрабатывает оборудование, и затем заканчивает обработку прерывания.
  • IST вызывает функцию InterruptDone, которая в свою очередь вызывает в OAL функцию OEMInterruptDone.
  • Функция OEMInterruptDone снова включает текущее прерывание. Драйвер устройства должен выполнить при загрузке следующие действия:

  • Регистрирует свой ISR в ядре. Драйвер должен зарегистрировать свой ISR в ядре, если драйвер не использует общую функцию OAL ISR для обработки своего прерывания. Драйвер должен зарегистрировать свой ISR в ядре, чтобы ядро вызывало ISR, когда происходит соответствующее физическое прерывание. Отобразить IRQ в SYSINTR в функции OEMInit из OAL. -или- Драйвер шины, который загружает драйвер, должен отобразить IRQ в SYSINTR, что делается в случае драйвера шины PCI.
  • Если драйвер не устанавливает ISR, любые прерывания, сгенерированные устройством, обрабатываются используемым по умолчанию ISR, который устанавливается OAL в OEMInit.
  • Процесс регистрации обработчика прерываний регистрирует событие, которое ассоциируется с системным прерыванием SYSINTR. После загрузки драйвера устройства драйвер создает поток службы прерываний (IST), а затем вызывает InterruptInitialize для регистрации события. IST может затем использовать функцию WaitForSingleObject для ожидания этого события и регистрации его в обработчике прерываний. IST регистрируется для одного или нескольких логических прерываний ( SYSINTR ).

    Если для определенного драйвера используется реализация компании Microsoft модельного драйвера устройства (MDD), вам не нужно будет писать код для регистрации прерывания. Слой MDD драйвера регистрирует драйвер для прерываний. Если создается монолитный драйвер, нужно реализовать код для регистрации IST драйвера в обработчике прерываний. Для этого используется функция CreateEvent для создания события и функция InterruptInitialize для соединения события с SYSINTR.

    Если драйвер устройства должен остановить обработку прерывания, то драйвер может использовать функцию InterruptDisable. Когда драйвер вызывает эту функцию, обработчик прерываний удаляет ассоциацию между IST и указанным логическим прерыванием. Обработчик прерываний выполняет это, вызывая функцию OEMInterruptDisable для отключения прерывания. Драйвер, если понадобиться, может позже снова зарегистрироваться для прерывания.

    Могут возникать ситуации, когда требуется передать информацию между процедурой службы прерываний (ISR) и потоком службы прерываний (IST). Например, потому что вызов IST всякий раз, когда приходит запрос прерывания (IRQ), является длительным процессом, можно спроектировать ISR с буферизацией данных IRQ перед вызовом IST.

    ISR будет возвращать SYSINTR_NOP, пока буфер не будет заполнен, а затем вернет соответствующий идентификатор SYSINTR, когда ISR будет готова для выполнения IST. Во время выполнения IST она может выбирать данные, которые буферизировала ISR.

    Для передачи данных между ISR и IST:

  • Зарезервируйте физическую память для ISR в файле Config.bib. Config.bib содержит несколько примеров резервирования физической памяти для последовательного и отладочного драйверов.
  • Используйте зарезервированную память в вызове ISR. Так как ISR выполняется в режиме ядра, ISR может обращаться к зарезервированной памяти для буферизации данных.
  • Вызовите функцию MmMapIoSpace в IST для отображения физической памяти в виртуальные адреса.

    Функция MMMapIoSpace вызывает функции VirtualAlloc и VirtualCopy для отображения физической памяти в адреса виртуальной памяти, к которой может обращаться IST.

    Можно также вызывать функции VirtualAlloc и VirtualCopy непосредственно. Например, можно выделить память вне пространства виртуальной памяти процесса, вызывая VirtualAlloc с параметрами, заданными следующим образом:

  • dwSize >= 2 MB
  • flAllocationType задается как MEM_RESERVE
  • flProtect задается как PAGE_NOACCESS
  • В Windows Embedded CE устанавливаемая ISR может легко использовать данные совместно с IST, так как память может распределяться динамически, вместо резервирования в файле Config.bib. Поток службы прерываний (IST) является потоком, который выполняет большую часть обработки прерываний. ОС пробуждает IST, когда ОС получает прерывание для обработки. Иначе IST находится в спящем режиме. IST для клавиатуры, сенсорного экрана, мыши, и драйвера дисплея должны вызывать функцию SetKMode(TRUE), чтобы позволить GWES писать в общую кучу памяти.

    Чтобы ОС пробудила IST, IST должна ассоциировать объект события с идентификатором прерывания. Используйте функцию CreateEvent для создания объекта события. После обработки прерывания IST должна ждать следующий сигнал прерывания. Этот вызов обычно делается в цикле.

    Когда происходит аппаратное прерывание, ядро сигнализирует о событии от имени ISR, и затем IST выполняет необходимые операции В/В на устройстве для сбора данных и их обработки. Когда обработка прерывания завершается, IST должна информировать ядро, чтобы снова включить аппаратное прерывание.

    Обычно потоки IST выполняются с приоритетом выше нормального. Они могут задать для себя более высокий приоритет перед регистрацией своего события в ядре, вызывая функцию CeSetThreadPriority. Таблица 9.3 показывает функции, которые обычно использует IST.

    Функции, используемые в IST
    Функция Описание
    InterruptInitialize Соединяет событие с идентификатором прерывания ISR.
    WaitForSingleObject Возвращает управление, когда указанный объект находится в сигнальном состоянии или когда истекает интервал перерыва.
    InterruptDone Инструктирует ядро о повторном включении аппаратного прерывания, связанного с этим потоком.

    Следующий список содержит примеры того, что IST может сделать при запуске:

  • Создать структуру для хранения значений прерываний.
  • Использовать CreateEvent в качестве триггера IST.
  • Прочитать значения IRQ и SysIntr из реестра и позволить OAL отобразить IRQ в значение SYSINTR перед загрузкой драйвера.
  • Сохранить указатель для созданного потока.
  • Следующий пример кода драйвера устройства клавиатуры PS/2 из ..\WINCE600\Public\Common\OAK\Drivers\Keybd\Ps2_8042\Ps2keybd.cpp показывает типичную IST.

    BOOL
    Ps2Keybd::
    IsrThreadProc(
      void
      )
    {
      DWORD dwTransferred = 0;
      HKEY hk;
      DWORD dwStatus, dwSize, dwValue, dwType;
      int iPriority = 145;
    
      // ищем свой приоритет в реестре 
      dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\KEYBD"), 0, 0, hk);
      if(dwStatus == ERROR_SUCCESS) {
      // Смотрим, можем ли мы включить прерывание, чтобы пробудить 
      // систему из приостановленного состояния.
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("EnableWake"), NULL, dwType, (LPBYTE) dwValue, dwSize);
        if(dwStatus == ERROR_SUCCESS  dwType == REG_DWORD) {
          if (dwValue != 0) {
            m_pp2p->SetWake(TRUE);
          }
        }
    
        // получаем приоритет потока прерывания 
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("Priority256"), NULL, dwType, (LPBYTE) dwValue, dwSize);
        if(dwStatus == ERROR_SUCCESS  dwType == REG_DWORD) {
          iPriority = (int) dwValue;
        }
    
        // считываем sysintr
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("SysIntr"), NULL, dwType, (LPBYTE) dwValue, dwSize);
        if(dwStatus == ERROR_SUCCESS) {
          if(dwType == REG_DWORD) {
            dwSysIntr_Keybd = dwValue;
          } else {
          dwStatus = ERROR_INVALID_PARAMETER;
          }
        }
        RegCloseKey(hk);
      }
    
      if(dwStatus != ERROR_SUCCESS) {
        goto leave;
      }
    
      // задаем приоритет потока 
      CeSetThreadPriority(GetCurrentThread(), iPriority);
    
      m_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
      if ( m_hevInterrupt == NULL)
      {
        goto leave;
      }
    
      if ( !InterruptInitialize(dwSysIntr_Keybd, m_hevInterrupt, NULL, 0) )
      {
        goto leave;
      }
    
      if (m_pp2p->WillWake()) {
    // Запрашиваем OAL включить прерывание, чтобы пробудить систему из приостановленного состояния.
        DEBUGMSG(ZONE_INIT, (TEXT("Keyboard: Enabling wake from suspend\r\n")));
        BOOL fErr = KernelIoControl(IOCTL_HAL_ENABLE_WAKE, dwSysIntr_Keybd,  
        sizeof(dwSysIntr_Keybd), NULL, 0, dwTransferred);
        DEBUGCHK(fErr); // KernelIoControl всегда должна выполняться успешно.
    
      } 
    
      m_pp2p -> KeybdInterruptEnable();
    
      extern UINT v_uiPddId;
      extern PFN_KEYBD_EVENT v_pfnKeybdEvent;
    
      KEYBD_IST keybdIst;
      keybdIst.hevInterrupt = m_hevInterrupt;
      keybdIst.dwSysIntr_Keybd = dwSysIntr_Keybd;
      keybdIst.uiPddId = v_uiPddId;
      keybdIst.pfnGetKeybdEvent = KeybdPdd_GetEventEx2;
      keybdIst.pfnKeybdEvent = v_pfnKeybdEvent;
    
        KeybdIstLoop(keybdIst);
    
    leave:
        return 0;
    }

    Следующий образец кода показывает реализацию функции KeybdIstLoop.

    BOOL
     KeybdIstLoop (
        PKEYBD_IST pKeybdIst
        )
    {
        SETFNAME(_T("KeybdIstLoop"));
    
        UINT32  rguiScanCode[16];
        BOOL    rgfKeyUp[16];
        UINT    cEvents;
    
        DEBUGCHK(pKeybdIst->hevInterrupt != NULL);
        DEBUGCHK(pKeybdIst->pfnGetKeybdEvent != NULL);
        DEBUGCHK(pKeybdIst->pfnKeybdEvent != NULL);
    
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
    
    wait_for_keybd_interrupt:
        if (WaitForSingleObject(pKeybdIst->hevInterrupt, INFINITE) == WAIT_OBJECT_0)
        {
            cEvents = (*pKeybdIst->pfnGetKeybdEvent)
                (pKeybdIst->uiPddId, rguiScanCode, rgfKeyUp);
                
            for (UINT iEvent = 0; iEvent < cEvents; ++iEvent) {
                (*pKeybdIst->pfnKeybdEvent)(pKeybdIst->uiPddId, 
                    rguiScanCode[iEvent], rgfKeyUp[iEvent]);
            }
       // cEvents может быть 0, если это был частичный скан-код, такой как 0xE0.
    
            InterruptDone(pKeybdIst->dwSysIntr_Keybd);
        }
    
        goto wait_for_keybd_interrupt;
    
        ERRORMSG(1, (TEXT("KeybdIstLoop: Keyboard driver thread terminating.\r\n")));
        return TRUE;
    }

    Операции DMA в драйверах устройств

    Прямой доступ к памяти (DMA) является методом переноса данных из устройства в память, из памяти в устройство, или из памяти в память, без помощи процессора. Можно выполнять DMA к общему буферу и вразброс/слитно с помощью CEDDK.dll или функций ядра. DMA к общему буферу использует непрерывный буфер в основной памяти. DMA вразброс/слитно использует множество блоков с различными адресами памяти.

    Стандартный перенос DMA происходит, когда контроллер DMA выполняет пересылку. Передача контроллера шины DMA происходит, когда периферийное устройство выполняет передачу. Компания Microsoft рекомендует использовать для DMA функции CEDDK.dll. Функции CEDDK.dll вызывают функции ядра. Таблица 9.4 сравнивает два способа выполнения DMA, с помощью функций CEDDK.dll и с помощью функций ядра.

    Сравнение функций CEDDK и ядра для выполнения DMA
    Использование функций CEDDK.dll Использование функций ядра
    CEDDK.dll предоставляет следующие функции для получения буфера для передач DMA: HalAllocateCommonBuffer HalFreeCommonBuffer HalTranslateSystemAddress Ядро предоставляет следующие функции для получения буфера для передач DMA: AllocPhysMem FreePhysMem
    Функции CEDDK.dll могут управлять специфическими адресными передачами аппаратной платформы и шины. Вы должны управлять специфическими адресными передачами аппаратной платформы. Для передачи адреса можно вызывать функцию HalTranslateSystemAddress.
    Функции CEDDK.dll будут полезны для DMA общего буфера. Функции ядра могут быть полезны для DMA вразброс/слитно.
    Функции CEDDK.dll используют по умолчанию выравнивание памяти по 64 KB. Функции ядра позволяют изменять используемое по умолчанию выравнивание памяти.

    Функции CEDDK.dll управляют трансляциями адресов между системой и шиной PCI или шиной ISA для контроллера DMA. Можно поддерживать другие типы шин. Функции CEDDK.dll транслируют физический адрес RAM в соответствующий физический адрес относительно шины для контроллера DMA. Для настройки общего буфера для DMA контроллера шины, используя функции CEDDK.dll, драйвер устройства DMA контроллера шины может вызвать функцию HalAllocateCommonBuffer со структурой DMA_ADAPTER_OBJECT.

    Следующий пример кода показывает вызов функции HalAllocateCommonBuffer. Пример кода извлечен из драйвера ES1371, расположенного в каталоге . .\WINCE600\Public\ Common\OAK\Drivers\ WaveDev\PDD\ES1371.

    // Размещаем в стеке объект адаптера 
    DMA_ADAPTER_OBJECT AdapterObject; 
    AdapterObject.ObjectSize = sizeof(AdapterObject); 
    AdapterObject.InterfaceType = PCIBus; 
    AdapterObject.BusNumber = 0; 
    
    // Размещаем одностраничный 4 KB буфер вывода
    dma_out_page[0] = (PUCHAR) HalAllocateCommonBuffer(AdapterObject, 4096, dma_out_logical_address, FALSE);
    
    if (!dma_out_page[0]) { 
      ERRMSG("PDD_AudioInitialize: DMA Buffer Page Allocation Failed");
      return FALSE;
    }

    Если вызов функции HalAllocateCommonBuffer будет успешным, он возвращает размещенный буфер, или NULL, если вызов будет неудачным. Драйвер может использовать размещенный общий буфер как область хранения для передач DMA. Функция также возвращает физический адрес буфера относительно шины, который функция дополнительно предоставляет контроллеру DMA.

    Можно также использовать функции ядра AllocPhysMem и FreePhysMem для передач DMA общего буфера. Если вы используете функции ядра, вызовите функцию HalTranslateSystemAddress для трансляции адреса, который передается контроллеру DMA, чтобы избежать нарушения границ памяти. Эти функции получают параметр выравнивания, функции CEDDK.dll используют выравнивание по умолчанию 64 KB. Для примера общего буфера DMA с функциями ядра, смотрите каталог ..\WINCE600\Public\Common\Oak\Drivers\Usb\Hcd directory.

    Чтобы задать DMA вразброс/слитно, необходимо использовать одновременно несколько пар базовых адресов и длин. Драйвер ATAPI, размещенный в каталоге ..\WINCE600\Public\Common\Oak\Drivers\Block\Atapi является примером реализации DMA вразброс/слитно.

    Простой пример драйвера устройств для Ebox

    В качестве учебного примера простого драйвера потокового интерфейса мы разработаем теперь новый драйвер устройства для последовательного порта eBox. Он будет основываться на предыдущем примере программы C/C++ последовательного порта, который взаимодействует непосредственно с оборудованием последовательного порта. Вспомните, что она использует функции CEDDK.lib, которые пишут в и читают непосредственно порты В/В, соединенные с 16550 UART. Чтобы избежать путаницы с обычными драйверами последовательного порта COM устройства CE, которые не будут использоваться, этот пример драйвера будет использовать для последовательного порта новое имя KOM.

    Наш драйвер порта KOM должен быть задан как подпроект DLL (а не приложение), и он должен определять и экспортировать стандартные функции потокового интерфейса. Управление UART последовательного порта и операции В/В, необходимые внутри каждой из этих функций, идут прямо из кода предыдущего примера последовательного порта. Код драйвера потокового интерфейса показан ниже. Включен ряд сообщений OutputDebugString для отслеживания операций драйвера в отладочной сборке.

    // Это образец драйвера устройства с потоковым интерфейсом 
    // Учебный пример, предназначенный для иллюстрации, как работают 
    // потоковые драйверы, использующий оборудование 
    // последовательного порта В/В на целевой системе 
    // и показывающий использование READ_PORT_UCHAR и WRITE_PORT_UCHAR
    // из CE Device Driver Kit (CEDDK)
    //
    // Настройка для X86 PC  (CEPC)
    // используя оборудование последовательного порта, совместимое с 16550 UART 
    // 
    // Не предназначен для замены хорошего драйвера 
    // устройства последовательного порта!
    // Не использует прерывания, не имеет никаких задержек, и не 
    // предоставляет поддержку для всех свойств последовательного порта 
    // Будет обычно использовать вызовы API ОС для этой работы!
    //
    // Для демонстрации: Соедините Ebox COM2: с ПК с помощью 
    // null-модемного кабеля  
    // Запустите HyperTerminal на скорости 9600 бод, 8 битами данных,
    // 1 стоп битом, без контроля четности, и без управления потоком  
    
    #include "stdafx.h"
    #include <windows.h>
    
    // Для функций WRITE_PORT_UCHAR и READ_PORT_UCHAR 
    // необходимо добавить в файл sources раздел include: 
    //  ..\Wince600\ICOP_Vortex86_60A_x86\cesysgen\ddk\inc; \
    //  ..\Wince600\ICOP_Vortex86_60A_x86\cesysgen\oak\inc; \
    
    #include "ceddk.h"
    
    // Также необходимо включить CEDDK.lib в link (см. файл sources)
    // add $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\ceddk.lib
    // в записи TARGETLIBS 
    
    		// Объявляем стандартные внешние функции потокового драйвера
    __declspec(dllexport) extern DWORD KOM_Init(LPCTSTR pContext, LPCVOID lpvBusContext);
    __declspec(dllexport) extern BOOL KOM_Deinit( DWORD hDeviceContext );
    __declspec(dllexport) extern DWORD KOM_Open( DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode );
    __declspec(dllexport) extern BOOL KOM_Close( DWORD hOpenContext );
    __declspec(dllexport) extern BOOL KOM_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, 
      DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut );
    __declspec(dllexport) extern void KOM_PowerUp( DWORD hDeviceContext );
    __declspec(dllexport) extern void KOM_PowerDown( DWORD hDeviceContext );
    __declspec(dllexport) extern DWORD KOM_Read( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count );
    __declspec(dllexport) extern DWORD KOM_Write( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count );
    __declspec(dllexport) extern DWORD KOM_Seek( DWORD hOpenContext, long Amount, WORD Type );
    
    void DBGOut(DWORD dwValue);
    void Setup_UART (PUCHAR Data_Port_Address);
    void Write_Serial_Character (PUCHAR Data_Port_Address, UCHAR Serial_Data);
    UCHAR Read_Serial_Character (PUCHAR Data_Port_Address);
    
    PUCHAR Data_Port_Address;
    UCHAR  Serial_Input_Data = 0;
    
    // ----------------------------------------------------
    
    BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
    {
        switch(ul_reason_for_call) {
            case DLL_PROCESS_ATTACH:
                OutputDebugString(L"KOM_DRIVER - DLL_PROCESS_ATTACH\n");
            break;
            case DLL_PROCESS_DETACH:
                OutputDebugString(L"KOM_DRIVER - DLL_PROCESS_DETACH\n");
            break;
            case DLL_THREAD_ATTACH:
                OutputDebugString(L"KOM_DRIVER - DLL_THREAD_ATTACH\n");
            break;
            case DLL_THREAD_DETACH:
                OutputDebugString(L"KOM_DRIVER - DLL_THREAD_DETACH\n");
            break;
            default:
            break;
        }
    return TRUE;
    }
    
    // Инициализация потокового драйвера ...
     DWORD KOM_Init( LPCTSTR pContext, LPCVOID lpvBusContext)
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Init - Context: ");
      OutputDebugString(pContext);
      OutputDebugString(L"\n");
      OutputDebugString(L"DemoDriver - Exit KOM_Init\n");
      return 0x1234;
    }
    
    BOOL KOM_Deinit( DWORD hDeviceContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Deinit\n");
    
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Deinit\n");
      return TRUE;
    }
    
    // Открытие потокового драйвера 
    DWORD KOM_Open( DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode )
    {
      OutputDebugString(L"DemoDriver - KOM_Open\n");
      OutputDebugString(L"hDeviceContext - ");
      DBGOut(hDeviceContext);
      OutputDebugString(L"\n");
      
    	// Адрес порта данных для COM2:
    	Data_Port_Address = (PUCHAR)0x2F8;
    		// Настройка UART для 9600 бод  без прерываний с 8D NP 1S
    	Setup_UART(Data_Port_Address);
    
       OutputDebugString(L"DemoDriver - Exit KOM_Open\n");
    return 0x5678;
    }
    // Закрытие потокового драйвера 
    BOOL KOM_Close( DWORD hOpenContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Close\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
      // Добавить код функции закрытия здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Close\n");
    return TRUE;
    }
    // IOCTL потокового драйвера 
    BOOL KOM_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, 
      DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_IOControl\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
      // Добавить код функции IOCTL здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_IOControl\n");
    return TRUE;
    }
    // PowerUP потокового драйвера 
    void KOM_PowerUp( DWORD hDeviceContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_PowerUp\n");
      OutputDebugString(L"hDeviceContext - ");
      DBGOut(hDeviceContext);
      OutputDebugString(L"\n");
      // Добавить код функции PowerUP здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_PowerUp\n");
    }
    // PowerDown потокового драйвера 
    void KOM_PowerDown( DWORD hDeviceContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_PowerDown\n");
      OutputDebugString(L"hDeviceContext - ");
      DBGOut(hDeviceContext);
      OutputDebugString(L"\n");
      // Добавить код функции PowerDown здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_PowerDown\n");
    }
    // Read потокового драйвера  
    DWORD KOM_Read( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count )
    {
      ULONG i;      
      OutputDebugString(L"KOM_DRIVER - KOM_Read\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
    
    		// Адрес порта данных для COM2:
    	Data_Port_Address = (PUCHAR)0x2F8;
    		// Чтение последовательных данных 
    	for (i=0; i<Count; i++)
      	   pBuffer[i] = Read_Serial_Character(Data_Port_Address);
    	
    	OutputDebugString(L"KOM_DRIVER - Exit KOM_Read\n");
    
    return Count;
    }
    // Write потокового драйвера 
    DWORD KOM_Write( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count )
    {
      ULONG i;
      OutputDebugString(L"KOM_DRIVER - KOM_Write\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
    
    		// Адрес порта данных для COM2:
    	Data_Port_Address = (PUCHAR)0x2F8;
    		// Запись последовательных данных 
    	for (i=0; i<Count; i++)
    	{
    	    Write_Serial_Character(Data_Port_Address, pBuffer[i]);
    //		DBGOut((DWORD)pBuffer[i]);
    	}
    
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Write\n");
    
    return Count;
    }
    // Seek потокового драйвера  
    DWORD KOM_Seek( DWORD hOpenContext, long Amount, WORD Type )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Seek\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
      // Добавить код функции Seek здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Seek\n");
    
    return 0;
    }
    
    void DBGOut(DWORD dwValue)
    {
      TCHAR tcTemp[10];
      wsprintf(tcTemp,L"%ld",dwValue);
      OutputDebugString(tcTemp);
    }
    
    
    void Write_Serial_Character (PUCHAR Data_Port_Address, UCHAR Serial_Data)
    		// Запись символа в потоковый порт 
    {
    	UCHAR Status;
    		// Ожидаем бит готовности выхода TX =1
    		// Адрес порта статуса В/В равен адрес порта данных В/В + 5
    	do{
    		Status = READ_PORT_UCHAR(Data_Port_Address + 5);
            // если UART передает, что буфер полон, 
    // выпустить напоминание о кванте времени 
    		if ((Status  0x40) == 0) Sleep(0); 
    	} while ((Status  0x40) == 0);
    		// Запись данных в COM2:
        WRITE_PORT_UCHAR(Data_Port_Address, Serial_Data);
    	return;
    }
    
    UCHAR Read_Serial_Character (PUCHAR Data_Port_Address)
    		// Читаем символ из последовательного порта 
    {
    	UCHAR Serial_Data, Status;
    		// Ожидаем бит готовности входа RX =1
    		// Адрес порта статуса В/В равен адрес порта данных В/В + 5
    	do{
    		Status = READ_PORT_UCHAR(Data_Port_Address + 5);
           // если UART получает сообщение о пустом буфере 
    // создать напоминание о кванте времени 
    		if ((Status  0x01) == 0) Sleep(0); 
    	} while ((Status  0x01) == 0);
    		// Читаем новые последовательные данные 
    	Serial_Data = READ_PORT_UCHAR(Data_Port_Address);
    	return Serial_Data;
    }
    
    void Setup_UART (PUCHAR Data_Port_Address)
    {
    	UCHAR Temp;
    		//	Настройка UART на 9600 бод 8D,NP,1S, без прерываний
    		// Чтобы полностью понять это потребуется хороший справочник по 
    // оборудованию ПК и/или спецификация 16550 UART!
    		// Отключите COMx: прерывания (используйте программируемый В/В)
    	WRITE_PORT_UCHAR(Data_Port_Address + 1, 0);
    		// Задаем скорость в бодах как 9600 с помощью настроек 
    // делителя частоты 
    		// Включить задание режима делителя 
    	Temp = READ_PORT_UCHAR(Data_Port_Address + 3);			
    	WRITE_PORT_UCHAR(Data_Port_Address + 3, Temp | 0x83);
    		// Задание LSB делителя (примечание: 12 = 115200/9600)
    	WRITE_PORT_UCHAR(Data_Port_Address , 12);
    		// Задание MSB делителя
    	WRITE_PORT_UCHAR(Data_Port_Address + 1, 0);
    		// Возврат в нормальный рабочий режим (и задание 8D NP 1S)
    	Temp = READ_PORT_UCHAR(Data_Port_Address + 3);			
    	WRITE_PORT_UCHAR(Data_Port_Address + 3, Temp  0x03);
    
    	return;
    }

    Вспомните, что Device Manager проверяет в реестре имя устройства, переданное в вызов API CreateFile, чтобы определить, какой драйвер должен использоваться. Эту запись реестра необходимо настроить для нашего нового драйвера, редактируя файл platform.reg подпроекта. Файлы *.reg автоматически сливаются и копируются в начальный реестр на целевом устройстве. Сделайте двойной щелчок на файле *.reg, чтобы отредактировать его с помощью regedit, или отредактируйте его с помощью Notepad. Он должен содержать следующую запись реестра в файле *.reg подпроекта, чтобы менеджер устройств смог его найти:

    [HKEY_LOCAL_MACHINE\Drivers\BuiltIn\KOM_DRIVER]
    "Dll" = "KOM_Port.Dll"
    "Prefix" ="KOM"
    "Index"= dword:1
    "Order"= dword:0
    "FriendlyName" = "KOM Port Demo Driver"
    "Ioctl" = dword:0

    Существует много различных настроек реестра, которые управляют загрузкой драйвера. Большинство из них являются необязательными. Некоторые настройки реестра используются менеджером устройств и могут при желании использоваться всеми драйверами. Можно также добавить пользовательские записи реестра, на которые указывает непосредственно сам драйвер после загрузки. DLL является строковым значением, которое определяет имя файла dll для загрузки. Это значение является обязательным. Префикс является трехсимвольной строкой, которая дополняет имя драйвера. Это значение должно быть представлено в определенном порядке, чтобы иметь указатель файла на основе интерфейса драйвера. Значение Prefix является тем же значением, которое должно использоваться в точках входа потокового драйвера, если не задан флаг DEVFLAGS_NAKEDENTRIES.

    Order является значением dword, которое обеспечивает механизм поддержки порядка загрузки зависимостей. Драйверы будут загружаться в порядке, определенном ключом Order. Если этот ключ не существует, драйвер будет загружаться в конце. Order не должен использоваться, если отсутствует порядок загрузки зависимостей для разрешения.

    Index является значением dword, которое составляет цифровую часть имени устройства. Это значение является необязательным, так как менеджер устройств выберет следующее последовательное значение, если оно не существует. IClass обеспечивает механизм объявления возможностей для различных системных компонентов. Flags предоставляет механизм управления способом загрузки драйвера.

    DLL можно загружать в пространство процесса с помощью функций LoadLibrary или LoadDriver. В этом примере мы хотим разработать драйвер реального устройства, который является частью ядра. Для этого файл DLL нового драйвера также необходимо включить в образ ядра времени выполнения. Чтобы включить новый файл в ядро, откройте файл *.bib подпроекта драйвера и подтвердите/добавьте следующую запись:

    MODULES
    KOM_Port.dll  $(_FLATRELEASEDIR)\KOM_Port.dll      NK   SHK

    Этот файл автоматически задается в подпроектах. Затем подпроекту требуется файл *.def для представления своих функций. Создайте файл *.def, используя Notepad или редактор в Platform builder со следующим содержанием:

    LIBRARY DemoDriver
    
    EXPORTS
    KOM_Init
    KOM_Deinit
    KOM_Open
    KOM_Close
    KOM_IOControl
    KOM_PowerUp
    KOM_PowerDown
    KOM_Read
    KOM_Write
    KOM_Seek

    В качестве альтернативы созданию файла *.def можно объявить каждую внешнюю функцию с помощью "__declspec(dllexport) " и extern "c" (in C++) перед определением каждой внешней функции в исходном коде C++.

    Теперь после сборки файл DLL нового драйвера будет в ядре. Менеджер устройств сможет найти драйвер, когда делается вызов API CreateFile "KOM", выполняя поиск в реестре. Точки входа драйвера будут доступны для других программ.

    Теперь приложение должно протестировать новый драйвер KOM, вызывая его и проверяя, что менеджер устройств может найти драйвер, и что функции драйвера работают правильно. Еще один подпроект создается для KOM_Tester. Код KOM_Tester показан ниже:

    // KOM_Tester.cpp : Определяет точку входа для консольного приложения 
    //
    // Тестовая программа в/в файла последовательного порта для драйвера KOM_Port
    //
    // Для демонстрации: Соедините Ebox COM2: с ПК с помощью 
    // null-модемного кабеля
    // Выполните HyperTerminal со скоростью 9600 бод, 8 битами данных, 
    // 1 стоп-битом, без контроля четности и без контроля потока
    
    #include "stdafx.h"
    
    HANDLE hSerial;
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	DWORD cBytes_out, cBytes_in;
    
    	char cBuffer_out[] = "\f\n    
         Hello KOM Serial World!\n\rType something and watch it echo back\n\rCtrl C to exit\n\r";
    	TCHAR cBuffer_in[80];
    		// Вывод сообщения на консоли 
    	printf("\nOpening KOM2: Serial Port to test new KOM Driver - Type ctrl C on other device to exit\n\r");
    		// Открыть последовательный порт COM2: для чтения и записи
    		// Примечание: COM1: задан для отправки информации Отладчика
                // поэтому COM2 становится COM1
    hSerial = CreateFile(_T("KOM1:"), GENERIC_READ | GENERIC_WRITE,
           0, NULL, OPEN_EXISTING, 0, NULL);
    		// Проверка ошибок открытия файла 
    	if (hSerial == INVALID_HANDLE_VALUE){
    		printf("file open errors\n","%X", hSerial);
    		Sleep(4000);
    		return 0;
    	}
    		// Записываем заголовок в последовательный порт.
    	if (!WriteFile(hSerial, cBuffer_out, strlen(cBuffer_out),
             cBytes_out, NULL)) {
    		printf("file write errors\n");
    		Sleep(4000);
    		return 0;
    	}
    
    	cBuffer_in[0] = 0;		
    
    
    // Читаем символы, копируем на экран консоли и Echo (записываем) назад 
    	// Цикл, пока не вводится ctrl C (0x03) 
    	while (cBuffer_in[0] != 0x03){
    		// Повторное считывание любых последовательных данных и вывод
    		  if (ReadFile(hSerial, cBuffer_in, 1, cBytes_in, NULL)){
    			if (cBytes_in == 0) break;
    		// Вывод данных повторного считывания 
    			printf("%s",cBuffer_in, cBytes_in);
    		// Echo-передача символов назад отправителю
    if (!WriteFile(hSerial, cBuffer_in, cBytes_in,               
      cBytes_out, NULL)){
    				printf("\rfile write errors\n");
    				Sleep(4000);
    				return 0;
    				}
    		  }
    		
    	}
    		// Закрываем файл 
    	CloseHandle(hSerial);
        return 1;
    }

    После сборки новое ядро загружается и программа KOM_Tester выполняется для тестирования драйвера KOM_Port.DLL. Соединяются последовательные кабели и на системе разработки запускается HyperTerminal. Текстовый вывод на eBox и системе разработки должен совпадать, как и в предыдущих примерах программ с последовательным портом. В отладочной сборке сообщения отладчика в окне вывода будут указывать, когда вызываются различные точки входа KOM_XXXX.

    Следующие отладочные сообщения драйвера были выведены в окне вывода отладки во время выполнения KOM_Tester:

    Run Programs s KOM_Tester
    PB Debugger Loaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\OSDESIGN7\ 
     RELDIR\ ICOP_VORTEX86_60A_X86_RELEASE\KOM_TESTER.EXE'
    s KOM_Tester  22:33:23 11/25/2006 Eastern Standard Time
    End s KOM_Tester  22:33:23 11/25/2006 Eastern Standard Time
    
    PB Debugger Loaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\OSDESIGN7\ 
     RELDIR\ ICOP_VORTEX86_60A_X86_RELEASE\CONSOLE.DLL'
      61200 PID:400002 TID:4880012 DemoDriver - KOM_Open
      61200 PID:400002 TID:4880012 hDeviceContext - 
      61200 PID:400002 TID:4880012 4660
      61201 PID:400002 TID:4880012 
      61202 PID:400002 TID:4880012 DemoDriver - Exit KOM_Open
      61203 PID:400002 TID:4880012 KOM_DRIVER - KOM_Write
      61203 PID:400002 TID:4880012 hOpenContext - 
      61204 PID:400002 TID:4880012 22136
      61204 PID:400002 TID:4880012 
      61317 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Write
      61318 PID:400002 TID:4880012 KOM_DRIVER - KOM_Read
      61318 PID:400002 TID:4880012 hOpenContext - 
      61319 PID:400002 TID:4880012 22136
      61319 PID:400002 TID:4880012 
      69052 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Read
      69066 PID:400002 TID:4880012 KOM_DRIVER - KOM_Write
      69067 PID:400002 TID:4880012 hOpenContext - 
      69067 PID:400002 TID:4880012 22136
      69067 PID:400002 TID:4880012 
      69067 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Write
      69068 PID:400002 TID:4880012 KOM_DRIVER - KOM_Read
      69068 PID:400002 TID:4880012 hOpenContext - 
      69069 PID:400002 TID:4880012 22136
      69069 PID:400002 TID:4880012 
      69221 PID:400002 TID:4e80012 KOM_DRIVER - DLL_THREAD_DETACH
    .
    .
    .
    185145 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Read
    185159 PID:400002 TID:4880012 KOM_DRIVER - KOM_Close
    185160 PID:400002 TID:4880012 hOpenContext - 
    185160 PID:400002 TID:4880012 22136
    185162 PID:400002 TID:4880012 
    185162 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Close
    PB Debugger Unloaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\ 
        OSDESIGN7\RELDIR\ICOP_VORTEX86_60A_X86_RELEASE\CONSOLE.DLL'
    PB Debugger Unloaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\  
        OSDESIGN7\ RELDIR\ICOP_VORTEX86_60A_X86_RELEASE\KOM_TESTER.EXE'
    206855 PID:400002 TID:40b0002 KOM_DRIVER - DLL_THREAD_DETACH

    Как можно видеть из вывода отладки, была загружена тестовая программа. Во время выполнения она вызвала KOM_Open. Было сделано несколько вызовов KOM_Write и KOM_Read для пересылки введенных данных на последовательный порт, соединенный с Hyperterminal, выполняющимся на ПК системы разработки. Прежде чем программа закончится после получения символа ввода CTL+C в последней операции KOM_Read, она делает вызов KOM_Close. Также во время процесса начальной загрузки вызывается KOM_init.

    Драйверы производственного качества

    Для реального драйвера производственного качества, такого как поставляется вместе с ОС, понадобиться сделать еще большее число усовершенствований в примере простого драйвера KOM. Он должен разрешать только один вызов KOM_OPEN в данный момент времени, чтобы два приложения не получили одновременно доступ к последовательному порту. Должно быть разработано полное множество функций IOCTL для поддержки настройки различных значений скорости в бодах, битов данных, битов контроля четности, стоп битов, и режимов квитирования. Он должен проверять ошибки переполнения, формирования кадра, и контроля четности, сообщаемые UART. Для более высоких скоростей передачи он может быть полностью буферизирован и управляться прерываниями. Программные циклы ошибок задержки должны рассматриваться для любого аппаратного отказа, который будут вызывать зависание драйвера. Вызов KOM_CLOSE, за которым сразу следует KOM_OPEN с другой скоростью в бодах, может вызывать ошибки, так как несколько символов буферизуются внутри UART. Функция KOM_CLOSE или KOM_OPEN может ожидать, пока буферы UART станут пустыми, чтобы разрешить эту проблему. Многие вызовы API имеют также параметры, так что они могут быть заданы для синхронной работы (ожидать, пока завершится операция, чтобы вернуть управление), или асинхронной (возвращать управление до завершения операции). Менеджер ресурсов В/В должен быть информирован о портах В/В, используемых драйвером.

    Если устройство имеет несколько портов KOM, его можно преобразовать в многослойный драйвер устройства (слои MDD и PDD). Запись реестра нужна для каждого экземпляра порта KOM с отличным индексом массива устройств, и базовый адрес В/В, который будет использовать драйвер, также должен быть добавлен, как новый ключ реестра. Наконец, все эти новые свойства, должно быть хорошо прокомментированы, полностью документированы, и тщательно протестированы.

    Можно найти реальный исходный код драйвера последовательного устройства CE в нескольких модулях в основном каталоге общего исходного кода драйвера \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL.

    Библиотека DLL высоко-скоростного дополнительного драйвера последовательного порта, управляемого прерываниями доступна в файле \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL\ISR16550.dll. ISR16550.dll, минимизирует время сигнала потока службы прерываний (IST). Это позволяет выполнять более быструю передачу данных, так как существует некоторый штраф за планирование системой передачи данных IST.ISR16550.dll из аппаратного буфера UART в программный буфер получения (FIFO) типа первый вошел, первый вышел, и заполняет данные FIFO передачи оборудования UART из буфера FIFO программной передачи без какого-либо вмешательства IST. IST будет сигнализировать, только когда получающий буфер достигнет своего порогового значения, а буфер передачи будет пустым. IST сигнализирует также, когда входящий поток данных прерывается.

    Дополнительная информация

  • Оперативная справочная система в Visual Studio, предоставляемая Windows Embedded CE 6.0, содержит дополнительную информацию по разработке драйверов устройств. Исходный код широкого ассортимента связанных с ПК драйверов устройств В/В поставляется вместе с CE в каталоге WINCE600\PUBLIC\COMMON\OAK\DRIVERS
  • Общедоступные общественные проекты исходного кода Phidgets и Logitech Web-камеры являются прекрасным источником информации по драйверам USB. Адресом URL общедоступных общественных проектов исходного кода CE является: http://msdn2.microsoft.com/en-us/embedded/aa731151.aspx
  • Пример драйвера потокового интерфейса для CE 5.0 разработан и описан в статье, "SPOT the Geek and Windows CE Drivers" Майка Холла (Mike Hall) по адресу http://msdn2.microsoft.com/en-us/library/aa459176.aspx
  • Лабораторные упражнения

  • Запустите драйвер KOM и выполните пример тестовой программы драйвера KOM и воспроизведите результаты, описанные в тексте.
  • Модифицируйте драйвер KOM, так чтобы только один процесс мог успешно его открыть в данный момент времени. Второе открытие должно возвращать код ошибки. Разработайте новую тестовую программу, и проверьте, что это работает.
  • Добавьте свойства IOCTRL в драйвер KOM, которые позволят задавать скорость в бодах, контроль четности, и число стоп-битов. Разработайте новую тестовую программу и проверьте, что это работает.
  • Разработайте тестовую программу, которая изменяет скорость в бодах во время отправки постоянных структур данных. Если имеются какие-либо проблемы, такие как недопустимые или пропущенные символы, модифицируйте код, чтобы их разрешить.
  • Сконфигурируйте данные менеджера ресурсов В/В, так чтобы он знал о диапазоне адресов В/В, используемом драйвером KOM. Попытайтесь открыть оба порта KOM и COM, и посмотрите, не будут ли возникать ошибки открытия в связи с конфликтом адресов В/В.
  • Изучите исходный код примера управляемого прерываниями драйвера последовательного устройства 16550 и модифицируйте драйвер KOM, чтобы сделать его управляемым прерываниями.
  • Страницы:

    Эта телефонная система LG Electronics Voice over IP (VoIP) основывается на Windows Embedded CE. Фотография с разрешения Mike Hall.

    Введение в драйверы устройств В/В

    Драйверы устройств помогают создать слой абстракции между оборудованием и прикладными программами пользователя. Типичный пользователь, разрабатывающий приложение, не должен понимать низко-уровневые детали оборудования каждого интерфейса. Вместо этого прикладная программа вызывает API ОС, который вызывает драйвер устройства. Работая на более высоком уровне абстракции, используя ОС и драйверы устройств, программисты приложений могут быть более продуктивными. При использовании ОС и драйверов устройств приложения можно переносить на различные аппаратные платформы с меньшими усилиями, и система будет также более стабильной и безопасной. ОС является критически важной в планировании, размещении, и управлении устройствами, которые совместно используются несколькими процессами. В качестве примера представьте, что произойдет, если два приложения начнут одновременно посылать символьные данные непосредственно оборудованию принтера. В результате получится случайная смесь символов на каждой напечатанной странице.

    Большинство распространенных драйверов устройств поставляются вместе с ОС или доступны производителям микросхем, но многие нужно будет переносить на новую аппаратную платформу, так как даже небольшие изменения, такие как задание адреса порта В/В или уровня прерывания могут требовать небольших модификаций кода драйвера.

    Одной из проблем, постоянно встречающейся разработчикам встроенных систем, является необходимость разработки драйверов устройств для уникального оборудования, вводимого при проектировании каждой новой платы. Документация по интерфейсам драйверов и примеры драйверов устройств поставляются с каждой ОС. Детали реализации драйверов варьируются от ОС к ОС. Может также поставляться инструмент типа мастера, помогающий сгенерировать шаблонный код, необходимый в новом драйвере устройства для правильного взаимодействия с ОС. В некоторых случаях драйверы устройств покупаются даже у сторонних разработчиков или разрабатываются внешними консультантами. Написаны целые книги по теме написания драйверов устройств. Библиотеки драйверов устройств CE перечислены в таблице 9.1.

    Библиотеки драйверов устройств CE
    Библиотека Описание
    Собственные библиотеки микропроцессора Драйверы устройств для тесной интеграции собственных периферийных устройств микропроцессора. Например, микропроцессор ARM, и его дополнительная микросхема интегрирует многие периферийные устройства микропроцессора, такие как LCD, последовательный порт, USB Host, функцию USB, и т.д. SDB или аппаратная платформа, которая использует специальный микропроцессор будут использовать то же самое множество собственных драйверов микропроцессора. В Windows Embedded CE большинство микропроцессоров являются высоко интегрированными микропроцессорами, которые содержат много собственной периферии. Они связаны с так называемыми драйверами SOC (система-на-микросхеме) и находятся в ..\WINCE600 \Platform\Common\Src\SOC.
    Специфические для микропроцессора библиотеки поддержки OAL Функции OAL для устройств, таких как часы реального времени, таймер, и контроллер отладки Ethernet, который обычно присутствует в микропроцессоре. Эта библиотека минимизирует написанный код OAL. Они также называются драйверами SOC (система-на-микросхеме) и находятся в ..\WINCE600 \Platform\Common\Src\SOC.
    BSP или драйверы специфические для аппаратной платформы Драйверы устройств для периферии, которые являются специфическими для заданного SDB или аппаратной платформы. Эти драйверы имеют специфический для аппаратной платформы код и могут использоваться только на этой аппаратной платформе. Эти драйверы находятся в ..\WINCE600\Platform\<Hardware Platform Name> \Src\Drivers.
    Другие распространенные драйверы периферийных устройств Драйверы для стратегических периферийных наборов микросхем, которые обычно встречаются во многих SDB или конструкциях аппаратных платформ. Сюда входят такие устройства как Realtek RTL8139, базовый NE2000, наборы микросхем DEC/Intel 2114x Ethernet, MediaQ MQ200, наборы микросхем дисплея ATI Rage XL, и наборы микросхем аудио Ensoniq. Цель состоит в том, чтобы предоставить драйверы производственного качества для 50% стратегических наборов микросхем, которые покрывают более 75% аппаратных платформ, доступных на рынке. Они называются общими драйверами и находятся в . .\WINCE600\Public\Common\Oak\Drivers. Исходный код для модулей находится в каталоге Public.

    Модель потокового интерфейса для драйверов устройств

    ДрайверомPart of the material presented in this Chapter was obtained from or is derived from the CE 6.0 on-line help files, MEDC 2006 conference presentations by members of the Windows Embedded CE design team, and Microsoft’s Windows Embedded CE Professional Training Materials. Reprinted with Permission of Microsoft Corporation. с потоковым интерфейсом является любой драйвер, который предоставляет функции потокового интерфейса, независимо от типа устройства, которым управляет драйвер. Модель потокового интерфейса является простейшим способом реализации большинства драйверов пользователя, и приложения обращаются к ним с помощью обычных функций API файловой системы. Подобно многим свойствам ОС, подход на основе потокового интерфейса ведет свое начало из первых версий Unix.

    Потоковый интерфейс подходит для любого устройства В/В, которое можно логически считать источником или стоком данных. То есть, любое периферийное устройство, которое создает или потребляет потоки данных как свою основную функцию, является хорошим кандидатом для предоставления потокового интерфейса. Примером является устройство потокового порта. Примером устройства, которое не создает и не потребляет данные в традиционном смысле, будет устройство дисплея, и действительно, для управления оборудованием дисплея потоковый интерфейс не предоставляется.

    Потоковый интерфейс может использовать другой нижележащий драйвер устройства для доступа к физическим периферийным устройствам, которыми управляет драйвер, или он может обращаться к устройству непосредственно, если устройство отражено в память. Драйверы аудио-устройств для встроенного аудио-оборудования являются примером прямого доступа.

    Сами функции потокового интерфейса создаются таким образом, чтобы как можно ближе соответствовать семантике обычных интерфейсов прикладного программирования (API) файловых систем, таких как CreateFile, WriteFile, ReadFile, IOControl, и CloseHandle. В качестве побочного эффекта такого дизайна устройства, которые управляются потоковым интерфейсом, доступны для приложений через файловую систему; приложения взаимодействуют с драйвером, открывая специальные файлы в файловой системе.

    Драйверы потокового интерфейса могут иметь монолитную архитектуру или архитектуру с двумя слоями, MDD и PDD, как показано на рисунке 9.1.

    (рис 9.1) Две альтернативные архитектуры драйвера потокового интерфейса. Монолитный драйвер потокового интерфейса или Разделенный на слои драйвер потокового интерфейса. В разделенной на слои архитектуре драйвера в CE два слоя называются MDD и PDD

    Интерпретация устройств как специальных файлов распространена во многих операционных системах, включая настольные версии Microsoft Windows и даже первые версия Unix. Там устройства печати традиционно представлялись именами специальных файлов LPTx:, а последовательные порты именами специальных файлов COMx:, и т.д.

    Несмотря на базовые характеристики потокового интерфейса, его можно реализовать различным образом. Например, даже хотя потоковый интерфейс обычно создается для периферийных устройств независимыми поставщиками оборудования (IHV), производители оригинального оборудования (OEM) могут предоставлять потоковый интерфейс для определенных встроенных устройств.

    В некоторых менее распространенных случаях драйверы потокового интерфейса могут переупаковывать существующие ресурсы, обычно таким образом, который специфические приложения смогут более легко использовать. Например, такой тип драйвера потокового интерфейса может управлять приемником системы глобального позиционирования (GPS) с последовательным интерфейсом. В этом примере ISV может выбрать создание специального драйвера потокового интерфейса для работы в соединении с приложением отображения GPS. Многие приемники GPS соединяются с последовательными портами. Приложение отображения GPS может, поэтому, открыть специальный файл COMx:, соответствующий последовательному порту и непосредственно взаимодействовать с приемником GPS.

    Однако приемник GPS может предоставлять данные позиционирования в неудобном формате, или создатель приложения может захотеть сохранить скрытыми детали управления специальными моделями приемников GPS. Поэтому можно написать драйвер потокового интерфейса для обеспечения взаимодействия между приложением и приемником GPS. Драйвер будет взаимодействовать с приемником GPS как и раньше через специальный файл COMx:, но может переупаковывать данные позиционирования в более удобном формате для приложения. Драйвер может предоставлять свои собственные службы как специальный файл GPSx:, который будет открывать приложение, чтобы прочитать данные позиционирования.

    Драйвер потокового интерфейса получает команды от Device Manager или из приложений посредством вызовов файловой системы. Драйвер инкапсулирует всю информацию, которая необходима для трансляции этих команд в соответствующие действия на устройствах, которыми он управляет.

    Все драйверы потоковых интерфейсов, управляют ли они встроенными устройствами или устанавливаемыми устройствами, загружаются ли они во время начальной загрузки или загружаются динамически, имеют похожие взаимодействия с другими системными компонентами. Рисунок 9.2 показывает архитектуру драйверов потокового интерфейса для встроенных устройств, которые загружаются Device Manager во время начальной загрузки.

    Различные вопросы могут влиять на конструктивные решения во время реализации динамически подключаемой библиотеки (DLL) драйвера потокового интерфейса. Чтобы реализовать драйвер потокового интерфейса, создайте DLL, содержащую требуемые точки входа для драйверов, и затем решите, хотите ли вы реализовать одиночный или множественный доступ к драйверу.

    Можно реализовать драйвер только с точками входа Init и Deinit и без префикса устройства. Невозможно получить доступ к этому драйверу с помощью CreateFile. PCIBUS и RegEnum являются двумя примерами такого типа драйвера. Эти драйверы находятся в каталогах ..\WINCE600\Public\Common\OAK\Drivers\PCIbus и ..\WINCE600\Public\Common\ OAK\Drivers\RegEnum.

    (рис 9.2) Архитектура потокового интерфейса

    Если DEVFLAGS_NAKEDENTRIES определен в подключе реестра Flags драйвера, то имена точек входа могут быть лишены отличий; например, Open, Close, и т. д. Пример драйвера батареи, который находится в ..\WINCE600\Public\Common\OAK\Drivers\Battdrvr, является примером драйвера, который использует точки входа без отличий. Настройки реестра драйвера батареи должны тем не менее включать префикс.

    Реализации этих точек входа должны объявляться для экспорта из DLL, размещая __declspec(dllexport) перед объявлением функции. При разработке в C++, точки входа должны также объявляться как внешние "C".

    Таблица 9.2 показывает функции драйвера потокового интерфейса с описанием назначения каждой из них. XXX в имени каждой функции заменяется трехсимвольным именем устройства. Эти функции определяются внутренне только для менеджера устройств, а не для приложений пользователя.

    Функции драйвера потокового интерфейса.
    Программный элемент Описание
    XXX_Close Эта функция закрывает контекст устройства, определенный hOpenContext. Эта функция требуется для доступа к устройству с помощью CreateFile. Если вы реализуете XXX_Close, вы должны реализовать XXX_Open.
    XXX_Deinit Эта функция де-инициализирует устройство. Ее вызывает Device Manager. Эта функция требуется драйверам, загружаемым ActivateDeviceEx, ActivateDevice, или RegisterDevice.
    XXX_Init Эта функция инициализирует устройство. Ее вызывает Device Manager. Эта функция требуется драйверам, загружаемым ActivateDeviceEx, ActivateDevice, или RegisterDevice.
    XXX_IOControl Эта функция посылает команду на устройство. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.
    XXX_Open Эта функция открывает устройство для чтения, записи, или того и другого. Приложение неявно вызывает эту функцию, когда вызывает CreateFile для получения указателя на устройство. Эта функция требуется для доступа к устройству с помощью CreateFile.
    XXX_PowerDown Не обязательная. Эта функция выключает питание устройства. Она будет полезна только с устройствами, которые можно выключить с помощью программного управления.
    XXX_PowerUp Не обязательная. Эта функция восстанавливает питание в устройстве.
    XXX_PreClose Не обязательная. Эта функция помечает закрывающий указатель как недействительный, и пробуждает все спящие потоки.
    XXX_PreDeinit Эта функция помечает экземпляр устройства как недействительный и пробуждает спящие потоки. Эта функция требуется, если реализована функция XXX_PreClose.
    XXX_Read Эта функция считывает данные из устройства, идентифицированного открытым контекстом. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.
    XXX_Seek Эта функция перемещает указатель данных в устройстве. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.
    XXX_Write Эта функция записывает данные в устройство. Эта функция может требоваться или нет, в зависимости от возможностей устройства, которые предоставляет драйвер. Эта функция требует реализации XXX_Open и XXX_Close.

    Так как периферийные устройства доступны приложениям как файлы, когда создается потоковый интерфейс, то нужно предоставить реализацию такого файла. Поэтому, рассмотрите, будет ли это практично, на основе возможностей устройства, которые предоставляет драйвер, чтобы позволить нескольким приложениям иметь одновременный доступ к файлу, который представляет устройство. То есть, рассмотрите, может ли драйвер иметь несколько открытых указателей файлов на своем устройстве. Драйвер потокового интерфейса может реализовать одиночный доступ или множественный доступ, используя параметр hOpenContext, передаваемый всем функциям В/В файла.

    Чтобы реализовать множественный доступ, каждый вызов функции XXX_Open должен возвращать различное значение для hOpenContext. Драйвер устройства должен отслеживать, какие возвращаемые значения из XXX_Open используются. Последующие обращения к XXX_Close, XXX_Read, XXX_Write, XXX_Seek, и XXX_IOControl передают эти значения назад драйверу устройства, позволяя драйверу идентифицировать, какими внутренними структурами данных манипулировать.

    Для реализации одиночного доступа, только первый вызов XXX_Open должен возвращать действительное значение hOpenContext. Пока это значение остается действительным, что будет иметь место, пока для этого значения не будет вызвана функция XXX_Close, последующие обращения к XXX_Open должны возвращать NULL вызывающему приложению, чтобы указать на отказ.

    Обработка ISR и IST для драйверов устройств

    Большинство периферийных устройств могут генерировать прерывания, чтобы получить обслуживание от операционной системы. Некоторыми примерами устройств, которые могут использовать прерывания, являются платы ПК, встроенные таймеры, устройства аудио-ввода, клавиатуры, сенсорные экраны, последовательные порты, и устройства указатели. Почти любой тип периферийного устройства может использовать прерывания в качестве основного метода инициирования действий ОС по обслуживанию.

    Так как эти периферийные устройства могут вызывать или сигнализировать о прерываниях, их драйверы устройств должны обрабатывать прерывания, чтобы обслужить свои устройства. Физические прерывания (IRQ) являются аппаратными линиями связи, по которым устройства могут посылать сигналы прерываний в микропроцессор. Логические прерывания (SYSINTR) являются отображением IRQ, которое определяет OAL.

    Когда обрабатывается прерывание, имеет место определенная последовательность событий. Необходимо написать для драйвера устройства запрос на обслуживание прерывания (ISR) и поток обслуживания прерывания (IST), помня о следующей последовательности событий.

  • Когда происходит прерывание, микропроцессор переходит к обработчику исключительных ситуаций ядра.
  • Обработчик исключительных ситуаций отключает все прерывания равного или более низкого приоритета микропроцессора, и затем вызывает соответствующий ISR для запроса физического прерывания (IRQ).
  • ISR возвращает логическое прерывание в форме идентификатора прерывания обработчику прерываний и, обычно, маскирует прерывание устройства уровня платы.
  • Обработчик прерываний снова включает все прерывания в микропроцессоре, за исключением текущего прерывания, которое остается замаскированным на плате, и затем сигнализирует о соответствующем событии IST.
  • IST планируется, обрабатывает оборудование, и затем заканчивает обработку прерывания.
  • IST вызывает функцию InterruptDone, которая в свою очередь вызывает в OAL функцию OEMInterruptDone.
  • Функция OEMInterruptDone снова включает текущее прерывание. Драйвер устройства должен выполнить при загрузке следующие действия:

  • Регистрирует свой ISR в ядре. Драйвер должен зарегистрировать свой ISR в ядре, если драйвер не использует общую функцию OAL ISR для обработки своего прерывания. Драйвер должен зарегистрировать свой ISR в ядре, чтобы ядро вызывало ISR, когда происходит соответствующее физическое прерывание. Отобразить IRQ в SYSINTR в функции OEMInit из OAL. -или- Драйвер шины, который загружает драйвер, должен отобразить IRQ в SYSINTR, что делается в случае драйвера шины PCI.
  • Если драйвер не устанавливает ISR, любые прерывания, сгенерированные устройством, обрабатываются используемым по умолчанию ISR, который устанавливается OAL в OEMInit.
  • Процесс регистрации обработчика прерываний регистрирует событие, которое ассоциируется с системным прерыванием SYSINTR. После загрузки драйвера устройства драйвер создает поток службы прерываний (IST), а затем вызывает InterruptInitialize для регистрации события. IST может затем использовать функцию WaitForSingleObject для ожидания этого события и регистрации его в обработчике прерываний. IST регистрируется для одного или нескольких логических прерываний ( SYSINTR ).

    Если для определенного драйвера используется реализация компании Microsoft модельного драйвера устройства (MDD), вам не нужно будет писать код для регистрации прерывания. Слой MDD драйвера регистрирует драйвер для прерываний. Если создается монолитный драйвер, нужно реализовать код для регистрации IST драйвера в обработчике прерываний. Для этого используется функция CreateEvent для создания события и функция InterruptInitialize для соединения события с SYSINTR.

    Если драйвер устройства должен остановить обработку прерывания, то драйвер может использовать функцию InterruptDisable. Когда драйвер вызывает эту функцию, обработчик прерываний удаляет ассоциацию между IST и указанным логическим прерыванием. Обработчик прерываний выполняет это, вызывая функцию OEMInterruptDisable для отключения прерывания. Драйвер, если понадобиться, может позже снова зарегистрироваться для прерывания.

    Могут возникать ситуации, когда требуется передать информацию между процедурой службы прерываний (ISR) и потоком службы прерываний (IST). Например, потому что вызов IST всякий раз, когда приходит запрос прерывания (IRQ), является длительным процессом, можно спроектировать ISR с буферизацией данных IRQ перед вызовом IST.

    ISR будет возвращать SYSINTR_NOP, пока буфер не будет заполнен, а затем вернет соответствующий идентификатор SYSINTR, когда ISR будет готова для выполнения IST. Во время выполнения IST она может выбирать данные, которые буферизировала ISR.

    Для передачи данных между ISR и IST:

  • Зарезервируйте физическую память для ISR в файле Config.bib. Config.bib содержит несколько примеров резервирования физической памяти для последовательного и отладочного драйверов.
  • Используйте зарезервированную память в вызове ISR. Так как ISR выполняется в режиме ядра, ISR может обращаться к зарезервированной памяти для буферизации данных.
  • Вызовите функцию MmMapIoSpace в IST для отображения физической памяти в виртуальные адреса.

    Функция MMMapIoSpace вызывает функции VirtualAlloc и VirtualCopy для отображения физической памяти в адреса виртуальной памяти, к которой может обращаться IST.

    Можно также вызывать функции VirtualAlloc и VirtualCopy непосредственно. Например, можно выделить память вне пространства виртуальной памяти процесса, вызывая VirtualAlloc с параметрами, заданными следующим образом:

  • dwSize >= 2 MB
  • flAllocationType задается как MEM_RESERVE
  • flProtect задается как PAGE_NOACCESS
  • В Windows Embedded CE устанавливаемая ISR может легко использовать данные совместно с IST, так как память может распределяться динамически, вместо резервирования в файле Config.bib. Поток службы прерываний (IST) является потоком, который выполняет большую часть обработки прерываний. ОС пробуждает IST, когда ОС получает прерывание для обработки. Иначе IST находится в спящем режиме. IST для клавиатуры, сенсорного экрана, мыши, и драйвера дисплея должны вызывать функцию SetKMode(TRUE), чтобы позволить GWES писать в общую кучу памяти.

    Чтобы ОС пробудила IST, IST должна ассоциировать объект события с идентификатором прерывания. Используйте функцию CreateEvent для создания объекта события. После обработки прерывания IST должна ждать следующий сигнал прерывания. Этот вызов обычно делается в цикле.

    Когда происходит аппаратное прерывание, ядро сигнализирует о событии от имени ISR, и затем IST выполняет необходимые операции В/В на устройстве для сбора данных и их обработки. Когда обработка прерывания завершается, IST должна информировать ядро, чтобы снова включить аппаратное прерывание.

    Обычно потоки IST выполняются с приоритетом выше нормального. Они могут задать для себя более высокий приоритет перед регистрацией своего события в ядре, вызывая функцию CeSetThreadPriority. Таблица 9.3 показывает функции, которые обычно использует IST.

    Функции, используемые в IST
    Функция Описание
    InterruptInitialize Соединяет событие с идентификатором прерывания ISR.
    WaitForSingleObject Возвращает управление, когда указанный объект находится в сигнальном состоянии или когда истекает интервал перерыва.
    InterruptDone Инструктирует ядро о повторном включении аппаратного прерывания, связанного с этим потоком.

    Следующий список содержит примеры того, что IST может сделать при запуске:

  • Создать структуру для хранения значений прерываний.
  • Использовать CreateEvent в качестве триггера IST.
  • Прочитать значения IRQ и SysIntr из реестра и позволить OAL отобразить IRQ в значение SYSINTR перед загрузкой драйвера.
  • Сохранить указатель для созданного потока.
  • Следующий пример кода драйвера устройства клавиатуры PS/2 из ..\WINCE600\Public\Common\OAK\Drivers\Keybd\Ps2_8042\Ps2keybd.cpp показывает типичную IST.

    BOOL
    Ps2Keybd::
    IsrThreadProc(
      void
      )
    {
      DWORD dwTransferred = 0;
      HKEY hk;
      DWORD dwStatus, dwSize, dwValue, dwType;
      int iPriority = 145;
    
      // ищем свой приоритет в реестре 
      dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\KEYBD"), 0, 0, hk);
      if(dwStatus == ERROR_SUCCESS) {
      // Смотрим, можем ли мы включить прерывание, чтобы пробудить 
      // систему из приостановленного состояния.
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("EnableWake"), NULL, dwType, (LPBYTE) dwValue, dwSize);
        if(dwStatus == ERROR_SUCCESS  dwType == REG_DWORD) {
          if (dwValue != 0) {
            m_pp2p->SetWake(TRUE);
          }
        }
    
        // получаем приоритет потока прерывания 
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("Priority256"), NULL, dwType, (LPBYTE) dwValue, dwSize);
        if(dwStatus == ERROR_SUCCESS  dwType == REG_DWORD) {
          iPriority = (int) dwValue;
        }
    
        // считываем sysintr
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("SysIntr"), NULL, dwType, (LPBYTE) dwValue, dwSize);
        if(dwStatus == ERROR_SUCCESS) {
          if(dwType == REG_DWORD) {
            dwSysIntr_Keybd = dwValue;
          } else {
          dwStatus = ERROR_INVALID_PARAMETER;
          }
        }
        RegCloseKey(hk);
      }
    
      if(dwStatus != ERROR_SUCCESS) {
        goto leave;
      }
    
      // задаем приоритет потока 
      CeSetThreadPriority(GetCurrentThread(), iPriority);
    
      m_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
      if ( m_hevInterrupt == NULL)
      {
        goto leave;
      }
    
      if ( !InterruptInitialize(dwSysIntr_Keybd, m_hevInterrupt, NULL, 0) )
      {
        goto leave;
      }
    
      if (m_pp2p->WillWake()) {
    // Запрашиваем OAL включить прерывание, чтобы пробудить систему из приостановленного состояния.
        DEBUGMSG(ZONE_INIT, (TEXT("Keyboard: Enabling wake from suspend\r\n")));
        BOOL fErr = KernelIoControl(IOCTL_HAL_ENABLE_WAKE, dwSysIntr_Keybd,  
        sizeof(dwSysIntr_Keybd), NULL, 0, dwTransferred);
        DEBUGCHK(fErr); // KernelIoControl всегда должна выполняться успешно.
    
      } 
    
      m_pp2p -> KeybdInterruptEnable();
    
      extern UINT v_uiPddId;
      extern PFN_KEYBD_EVENT v_pfnKeybdEvent;
    
      KEYBD_IST keybdIst;
      keybdIst.hevInterrupt = m_hevInterrupt;
      keybdIst.dwSysIntr_Keybd = dwSysIntr_Keybd;
      keybdIst.uiPddId = v_uiPddId;
      keybdIst.pfnGetKeybdEvent = KeybdPdd_GetEventEx2;
      keybdIst.pfnKeybdEvent = v_pfnKeybdEvent;
    
        KeybdIstLoop(keybdIst);
    
    leave:
        return 0;
    }

    Следующий образец кода показывает реализацию функции KeybdIstLoop.

    BOOL
     KeybdIstLoop (
        PKEYBD_IST pKeybdIst
        )
    {
        SETFNAME(_T("KeybdIstLoop"));
    
        UINT32  rguiScanCode[16];
        BOOL    rgfKeyUp[16];
        UINT    cEvents;
    
        DEBUGCHK(pKeybdIst->hevInterrupt != NULL);
        DEBUGCHK(pKeybdIst->pfnGetKeybdEvent != NULL);
        DEBUGCHK(pKeybdIst->pfnKeybdEvent != NULL);
    
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
    
    wait_for_keybd_interrupt:
        if (WaitForSingleObject(pKeybdIst->hevInterrupt, INFINITE) == WAIT_OBJECT_0)
        {
            cEvents = (*pKeybdIst->pfnGetKeybdEvent)
                (pKeybdIst->uiPddId, rguiScanCode, rgfKeyUp);
                
            for (UINT iEvent = 0; iEvent < cEvents; ++iEvent) {
                (*pKeybdIst->pfnKeybdEvent)(pKeybdIst->uiPddId, 
                    rguiScanCode[iEvent], rgfKeyUp[iEvent]);
            }
       // cEvents может быть 0, если это был частичный скан-код, такой как 0xE0.
    
            InterruptDone(pKeybdIst->dwSysIntr_Keybd);
        }
    
        goto wait_for_keybd_interrupt;
    
        ERRORMSG(1, (TEXT("KeybdIstLoop: Keyboard driver thread terminating.\r\n")));
        return TRUE;
    }

    Операции DMA в драйверах устройств

    Прямой доступ к памяти (DMA) является методом переноса данных из устройства в память, из памяти в устройство, или из памяти в память, без помощи процессора. Можно выполнять DMA к общему буферу и вразброс/слитно с помощью CEDDK.dll или функций ядра. DMA к общему буферу использует непрерывный буфер в основной памяти. DMA вразброс/слитно использует множество блоков с различными адресами памяти.

    Стандартный перенос DMA происходит, когда контроллер DMA выполняет пересылку. Передача контроллера шины DMA происходит, когда периферийное устройство выполняет передачу. Компания Microsoft рекомендует использовать для DMA функции CEDDK.dll. Функции CEDDK.dll вызывают функции ядра. Таблица 9.4 сравнивает два способа выполнения DMA, с помощью функций CEDDK.dll и с помощью функций ядра.

    Сравнение функций CEDDK и ядра для выполнения DMA
    Использование функций CEDDK.dll Использование функций ядра
    CEDDK.dll предоставляет следующие функции для получения буфера для передач DMA: HalAllocateCommonBuffer HalFreeCommonBuffer HalTranslateSystemAddress Ядро предоставляет следующие функции для получения буфера для передач DMA: AllocPhysMem FreePhysMem
    Функции CEDDK.dll могут управлять специфическими адресными передачами аппаратной платформы и шины. Вы должны управлять специфическими адресными передачами аппаратной платформы. Для передачи адреса можно вызывать функцию HalTranslateSystemAddress.
    Функции CEDDK.dll будут полезны для DMA общего буфера. Функции ядра могут быть полезны для DMA вразброс/слитно.
    Функции CEDDK.dll используют по умолчанию выравнивание памяти по 64 KB. Функции ядра позволяют изменять используемое по умолчанию выравнивание памяти.

    Функции CEDDK.dll управляют трансляциями адресов между системой и шиной PCI или шиной ISA для контроллера DMA. Можно поддерживать другие типы шин. Функции CEDDK.dll транслируют физический адрес RAM в соответствующий физический адрес относительно шины для контроллера DMA. Для настройки общего буфера для DMA контроллера шины, используя функции CEDDK.dll, драйвер устройства DMA контроллера шины может вызвать функцию HalAllocateCommonBuffer со структурой DMA_ADAPTER_OBJECT.

    Следующий пример кода показывает вызов функции HalAllocateCommonBuffer. Пример кода извлечен из драйвера ES1371, расположенного в каталоге . .\WINCE600\Public\ Common\OAK\Drivers\ WaveDev\PDD\ES1371.

    // Размещаем в стеке объект адаптера 
    DMA_ADAPTER_OBJECT AdapterObject; 
    AdapterObject.ObjectSize = sizeof(AdapterObject); 
    AdapterObject.InterfaceType = PCIBus; 
    AdapterObject.BusNumber = 0; 
    
    // Размещаем одностраничный 4 KB буфер вывода
    dma_out_page[0] = (PUCHAR) HalAllocateCommonBuffer(AdapterObject, 4096, dma_out_logical_address, FALSE);
    
    if (!dma_out_page[0]) { 
      ERRMSG("PDD_AudioInitialize: DMA Buffer Page Allocation Failed");
      return FALSE;
    }

    Если вызов функции HalAllocateCommonBuffer будет успешным, он возвращает размещенный буфер, или NULL, если вызов будет неудачным. Драйвер может использовать размещенный общий буфер как область хранения для передач DMA. Функция также возвращает физический адрес буфера относительно шины, который функция дополнительно предоставляет контроллеру DMA.

    Можно также использовать функции ядра AllocPhysMem и FreePhysMem для передач DMA общего буфера. Если вы используете функции ядра, вызовите функцию HalTranslateSystemAddress для трансляции адреса, который передается контроллеру DMA, чтобы избежать нарушения границ памяти. Эти функции получают параметр выравнивания, функции CEDDK.dll используют выравнивание по умолчанию 64 KB. Для примера общего буфера DMA с функциями ядра, смотрите каталог ..\WINCE600\Public\Common\Oak\Drivers\Usb\Hcd directory.

    Чтобы задать DMA вразброс/слитно, необходимо использовать одновременно несколько пар базовых адресов и длин. Драйвер ATAPI, размещенный в каталоге ..\WINCE600\Public\Common\Oak\Drivers\Block\Atapi является примером реализации DMA вразброс/слитно.

    Простой пример драйвера устройств для Ebox

    В качестве учебного примера простого драйвера потокового интерфейса мы разработаем теперь новый драйвер устройства для последовательного порта eBox. Он будет основываться на предыдущем примере программы C/C++ последовательного порта, который взаимодействует непосредственно с оборудованием последовательного порта. Вспомните, что она использует функции CEDDK.lib, которые пишут в и читают непосредственно порты В/В, соединенные с 16550 UART. Чтобы избежать путаницы с обычными драйверами последовательного порта COM устройства CE, которые не будут использоваться, этот пример драйвера будет использовать для последовательного порта новое имя KOM.

    Наш драйвер порта KOM должен быть задан как подпроект DLL (а не приложение), и он должен определять и экспортировать стандартные функции потокового интерфейса. Управление UART последовательного порта и операции В/В, необходимые внутри каждой из этих функций, идут прямо из кода предыдущего примера последовательного порта. Код драйвера потокового интерфейса показан ниже. Включен ряд сообщений OutputDebugString для отслеживания операций драйвера в отладочной сборке.

    // Это образец драйвера устройства с потоковым интерфейсом 
    // Учебный пример, предназначенный для иллюстрации, как работают 
    // потоковые драйверы, использующий оборудование 
    // последовательного порта В/В на целевой системе 
    // и показывающий использование READ_PORT_UCHAR и WRITE_PORT_UCHAR
    // из CE Device Driver Kit (CEDDK)
    //
    // Настройка для X86 PC  (CEPC)
    // используя оборудование последовательного порта, совместимое с 16550 UART 
    // 
    // Не предназначен для замены хорошего драйвера 
    // устройства последовательного порта!
    // Не использует прерывания, не имеет никаких задержек, и не 
    // предоставляет поддержку для всех свойств последовательного порта 
    // Будет обычно использовать вызовы API ОС для этой работы!
    //
    // Для демонстрации: Соедините Ebox COM2: с ПК с помощью 
    // null-модемного кабеля  
    // Запустите HyperTerminal на скорости 9600 бод, 8 битами данных,
    // 1 стоп битом, без контроля четности, и без управления потоком  
    
    #include "stdafx.h"
    #include <windows.h>
    
    // Для функций WRITE_PORT_UCHAR и READ_PORT_UCHAR 
    // необходимо добавить в файл sources раздел include: 
    //  ..\Wince600\ICOP_Vortex86_60A_x86\cesysgen\ddk\inc; \
    //  ..\Wince600\ICOP_Vortex86_60A_x86\cesysgen\oak\inc; \
    
    #include "ceddk.h"
    
    // Также необходимо включить CEDDK.lib в link (см. файл sources)
    // add $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\ceddk.lib
    // в записи TARGETLIBS 
    
    		// Объявляем стандартные внешние функции потокового драйвера
    __declspec(dllexport) extern DWORD KOM_Init(LPCTSTR pContext, LPCVOID lpvBusContext);
    __declspec(dllexport) extern BOOL KOM_Deinit( DWORD hDeviceContext );
    __declspec(dllexport) extern DWORD KOM_Open( DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode );
    __declspec(dllexport) extern BOOL KOM_Close( DWORD hOpenContext );
    __declspec(dllexport) extern BOOL KOM_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, 
      DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut );
    __declspec(dllexport) extern void KOM_PowerUp( DWORD hDeviceContext );
    __declspec(dllexport) extern void KOM_PowerDown( DWORD hDeviceContext );
    __declspec(dllexport) extern DWORD KOM_Read( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count );
    __declspec(dllexport) extern DWORD KOM_Write( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count );
    __declspec(dllexport) extern DWORD KOM_Seek( DWORD hOpenContext, long Amount, WORD Type );
    
    void DBGOut(DWORD dwValue);
    void Setup_UART (PUCHAR Data_Port_Address);
    void Write_Serial_Character (PUCHAR Data_Port_Address, UCHAR Serial_Data);
    UCHAR Read_Serial_Character (PUCHAR Data_Port_Address);
    
    PUCHAR Data_Port_Address;
    UCHAR  Serial_Input_Data = 0;
    
    // ----------------------------------------------------
    
    BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
    {
        switch(ul_reason_for_call) {
            case DLL_PROCESS_ATTACH:
                OutputDebugString(L"KOM_DRIVER - DLL_PROCESS_ATTACH\n");
            break;
            case DLL_PROCESS_DETACH:
                OutputDebugString(L"KOM_DRIVER - DLL_PROCESS_DETACH\n");
            break;
            case DLL_THREAD_ATTACH:
                OutputDebugString(L"KOM_DRIVER - DLL_THREAD_ATTACH\n");
            break;
            case DLL_THREAD_DETACH:
                OutputDebugString(L"KOM_DRIVER - DLL_THREAD_DETACH\n");
            break;
            default:
            break;
        }
    return TRUE;
    }
    
    // Инициализация потокового драйвера ...
     DWORD KOM_Init( LPCTSTR pContext, LPCVOID lpvBusContext)
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Init - Context: ");
      OutputDebugString(pContext);
      OutputDebugString(L"\n");
      OutputDebugString(L"DemoDriver - Exit KOM_Init\n");
      return 0x1234;
    }
    
    BOOL KOM_Deinit( DWORD hDeviceContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Deinit\n");
    
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Deinit\n");
      return TRUE;
    }
    
    // Открытие потокового драйвера 
    DWORD KOM_Open( DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode )
    {
      OutputDebugString(L"DemoDriver - KOM_Open\n");
      OutputDebugString(L"hDeviceContext - ");
      DBGOut(hDeviceContext);
      OutputDebugString(L"\n");
      
    	// Адрес порта данных для COM2:
    	Data_Port_Address = (PUCHAR)0x2F8;
    		// Настройка UART для 9600 бод  без прерываний с 8D NP 1S
    	Setup_UART(Data_Port_Address);
    
       OutputDebugString(L"DemoDriver - Exit KOM_Open\n");
    return 0x5678;
    }
    // Закрытие потокового драйвера 
    BOOL KOM_Close( DWORD hOpenContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Close\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
      // Добавить код функции закрытия здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Close\n");
    return TRUE;
    }
    // IOCTL потокового драйвера 
    BOOL KOM_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, 
      DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_IOControl\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
      // Добавить код функции IOCTL здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_IOControl\n");
    return TRUE;
    }
    // PowerUP потокового драйвера 
    void KOM_PowerUp( DWORD hDeviceContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_PowerUp\n");
      OutputDebugString(L"hDeviceContext - ");
      DBGOut(hDeviceContext);
      OutputDebugString(L"\n");
      // Добавить код функции PowerUP здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_PowerUp\n");
    }
    // PowerDown потокового драйвера 
    void KOM_PowerDown( DWORD hDeviceContext )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_PowerDown\n");
      OutputDebugString(L"hDeviceContext - ");
      DBGOut(hDeviceContext);
      OutputDebugString(L"\n");
      // Добавить код функции PowerDown здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_PowerDown\n");
    }
    // Read потокового драйвера  
    DWORD KOM_Read( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count )
    {
      ULONG i;      
      OutputDebugString(L"KOM_DRIVER - KOM_Read\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
    
    		// Адрес порта данных для COM2:
    	Data_Port_Address = (PUCHAR)0x2F8;
    		// Чтение последовательных данных 
    	for (i=0; i<Count; i++)
      	   pBuffer[i] = Read_Serial_Character(Data_Port_Address);
    	
    	OutputDebugString(L"KOM_DRIVER - Exit KOM_Read\n");
    
    return Count;
    }
    // Write потокового драйвера 
    DWORD KOM_Write( DWORD hOpenContext, PUCHAR pBuffer, ULONG Count )
    {
      ULONG i;
      OutputDebugString(L"KOM_DRIVER - KOM_Write\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
    
    		// Адрес порта данных для COM2:
    	Data_Port_Address = (PUCHAR)0x2F8;
    		// Запись последовательных данных 
    	for (i=0; i<Count; i++)
    	{
    	    Write_Serial_Character(Data_Port_Address, pBuffer[i]);
    //		DBGOut((DWORD)pBuffer[i]);
    	}
    
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Write\n");
    
    return Count;
    }
    // Seek потокового драйвера  
    DWORD KOM_Seek( DWORD hOpenContext, long Amount, WORD Type )
    {
      OutputDebugString(L"KOM_DRIVER - KOM_Seek\n");
      OutputDebugString(L"hOpenContext - ");
      DBGOut(hOpenContext);
      OutputDebugString(L"\n");
      // Добавить код функции Seek здесь 
      OutputDebugString(L"KOM_DRIVER - Exit KOM_Seek\n");
    
    return 0;
    }
    
    void DBGOut(DWORD dwValue)
    {
      TCHAR tcTemp[10];
      wsprintf(tcTemp,L"%ld",dwValue);
      OutputDebugString(tcTemp);
    }
    
    
    void Write_Serial_Character (PUCHAR Data_Port_Address, UCHAR Serial_Data)
    		// Запись символа в потоковый порт 
    {
    	UCHAR Status;
    		// Ожидаем бит готовности выхода TX =1
    		// Адрес порта статуса В/В равен адрес порта данных В/В + 5
    	do{
    		Status = READ_PORT_UCHAR(Data_Port_Address + 5);
            // если UART передает, что буфер полон, 
    // выпустить напоминание о кванте времени 
    		if ((Status  0x40) == 0) Sleep(0); 
    	} while ((Status  0x40) == 0);
    		// Запись данных в COM2:
        WRITE_PORT_UCHAR(Data_Port_Address, Serial_Data);
    	return;
    }
    
    UCHAR Read_Serial_Character (PUCHAR Data_Port_Address)
    		// Читаем символ из последовательного порта 
    {
    	UCHAR Serial_Data, Status;
    		// Ожидаем бит готовности входа RX =1
    		// Адрес порта статуса В/В равен адрес порта данных В/В + 5
    	do{
    		Status = READ_PORT_UCHAR(Data_Port_Address + 5);
           // если UART получает сообщение о пустом буфере 
    // создать напоминание о кванте времени 
    		if ((Status  0x01) == 0) Sleep(0); 
    	} while ((Status  0x01) == 0);
    		// Читаем новые последовательные данные 
    	Serial_Data = READ_PORT_UCHAR(Data_Port_Address);
    	return Serial_Data;
    }
    
    void Setup_UART (PUCHAR Data_Port_Address)
    {
    	UCHAR Temp;
    		//	Настройка UART на 9600 бод 8D,NP,1S, без прерываний
    		// Чтобы полностью понять это потребуется хороший справочник по 
    // оборудованию ПК и/или спецификация 16550 UART!
    		// Отключите COMx: прерывания (используйте программируемый В/В)
    	WRITE_PORT_UCHAR(Data_Port_Address + 1, 0);
    		// Задаем скорость в бодах как 9600 с помощью настроек 
    // делителя частоты 
    		// Включить задание режима делителя 
    	Temp = READ_PORT_UCHAR(Data_Port_Address + 3);			
    	WRITE_PORT_UCHAR(Data_Port_Address + 3, Temp | 0x83);
    		// Задание LSB делителя (примечание: 12 = 115200/9600)
    	WRITE_PORT_UCHAR(Data_Port_Address , 12);
    		// Задание MSB делителя
    	WRITE_PORT_UCHAR(Data_Port_Address + 1, 0);
    		// Возврат в нормальный рабочий режим (и задание 8D NP 1S)
    	Temp = READ_PORT_UCHAR(Data_Port_Address + 3);			
    	WRITE_PORT_UCHAR(Data_Port_Address + 3, Temp  0x03);
    
    	return;
    }

    Вспомните, что Device Manager проверяет в реестре имя устройства, переданное в вызов API CreateFile, чтобы определить, какой драйвер должен использоваться. Эту запись реестра необходимо настроить для нашего нового драйвера, редактируя файл platform.reg подпроекта. Файлы *.reg автоматически сливаются и копируются в начальный реестр на целевом устройстве. Сделайте двойной щелчок на файле *.reg, чтобы отредактировать его с помощью regedit, или отредактируйте его с помощью Notepad. Он должен содержать следующую запись реестра в файле *.reg подпроекта, чтобы менеджер устройств смог его найти:

    [HKEY_LOCAL_MACHINE\Drivers\BuiltIn\KOM_DRIVER]
    "Dll" = "KOM_Port.Dll"
    "Prefix" ="KOM"
    "Index"= dword:1
    "Order"= dword:0
    "FriendlyName" = "KOM Port Demo Driver"
    "Ioctl" = dword:0

    Существует много различных настроек реестра, которые управляют загрузкой драйвера. Большинство из них являются необязательными. Некоторые настройки реестра используются менеджером устройств и могут при желании использоваться всеми драйверами. Можно также добавить пользовательские записи реестра, на которые указывает непосредственно сам драйвер после загрузки. DLL является строковым значением, которое определяет имя файла dll для загрузки. Это значение является обязательным. Префикс является трехсимвольной строкой, которая дополняет имя драйвера. Это значение должно быть представлено в определенном порядке, чтобы иметь указатель файла на основе интерфейса драйвера. Значение Prefix является тем же значением, которое должно использоваться в точках входа потокового драйвера, если не задан флаг DEVFLAGS_NAKEDENTRIES.

    Order является значением dword, которое обеспечивает механизм поддержки порядка загрузки зависимостей. Драйверы будут загружаться в порядке, определенном ключом Order. Если этот ключ не существует, драйвер будет загружаться в конце. Order не должен использоваться, если отсутствует порядок загрузки зависимостей для разрешения.

    Index является значением dword, которое составляет цифровую часть имени устройства. Это значение является необязательным, так как менеджер устройств выберет следующее последовательное значение, если оно не существует. IClass обеспечивает механизм объявления возможностей для различных системных компонентов. Flags предоставляет механизм управления способом загрузки драйвера.

    DLL можно загружать в пространство процесса с помощью функций LoadLibrary или LoadDriver. В этом примере мы хотим разработать драйвер реального устройства, который является частью ядра. Для этого файл DLL нового драйвера также необходимо включить в образ ядра времени выполнения. Чтобы включить новый файл в ядро, откройте файл *.bib подпроекта драйвера и подтвердите/добавьте следующую запись:

    MODULES
    KOM_Port.dll  $(_FLATRELEASEDIR)\KOM_Port.dll      NK   SHK

    Этот файл автоматически задается в подпроектах. Затем подпроекту требуется файл *.def для представления своих функций. Создайте файл *.def, используя Notepad или редактор в Platform builder со следующим содержанием:

    LIBRARY DemoDriver
    
    EXPORTS
    KOM_Init
    KOM_Deinit
    KOM_Open
    KOM_Close
    KOM_IOControl
    KOM_PowerUp
    KOM_PowerDown
    KOM_Read
    KOM_Write
    KOM_Seek

    В качестве альтернативы созданию файла *.def можно объявить каждую внешнюю функцию с помощью "__declspec(dllexport) " и extern "c" (in C++) перед определением каждой внешней функции в исходном коде C++.

    Теперь после сборки файл DLL нового драйвера будет в ядре. Менеджер устройств сможет найти драйвер, когда делается вызов API CreateFile "KOM", выполняя поиск в реестре. Точки входа драйвера будут доступны для других программ.

    Теперь приложение должно протестировать новый драйвер KOM, вызывая его и проверяя, что менеджер устройств может найти драйвер, и что функции драйвера работают правильно. Еще один подпроект создается для KOM_Tester. Код KOM_Tester показан ниже:

    // KOM_Tester.cpp : Определяет точку входа для консольного приложения 
    //
    // Тестовая программа в/в файла последовательного порта для драйвера KOM_Port
    //
    // Для демонстрации: Соедините Ebox COM2: с ПК с помощью 
    // null-модемного кабеля
    // Выполните HyperTerminal со скоростью 9600 бод, 8 битами данных, 
    // 1 стоп-битом, без контроля четности и без контроля потока
    
    #include "stdafx.h"
    
    HANDLE hSerial;
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	DWORD cBytes_out, cBytes_in;
    
    	char cBuffer_out[] = "\f\n    
         Hello KOM Serial World!\n\rType something and watch it echo back\n\rCtrl C to exit\n\r";
    	TCHAR cBuffer_in[80];
    		// Вывод сообщения на консоли 
    	printf("\nOpening KOM2: Serial Port to test new KOM Driver - Type ctrl C on other device to exit\n\r");
    		// Открыть последовательный порт COM2: для чтения и записи
    		// Примечание: COM1: задан для отправки информации Отладчика
                // поэтому COM2 становится COM1
    hSerial = CreateFile(_T("KOM1:"), GENERIC_READ | GENERIC_WRITE,
           0, NULL, OPEN_EXISTING, 0, NULL);
    		// Проверка ошибок открытия файла 
    	if (hSerial == INVALID_HANDLE_VALUE){
    		printf("file open errors\n","%X", hSerial);
    		Sleep(4000);
    		return 0;
    	}
    		// Записываем заголовок в последовательный порт.
    	if (!WriteFile(hSerial, cBuffer_out, strlen(cBuffer_out),
             cBytes_out, NULL)) {
    		printf("file write errors\n");
    		Sleep(4000);
    		return 0;
    	}
    
    	cBuffer_in[0] = 0;		
    
    
    // Читаем символы, копируем на экран консоли и Echo (записываем) назад 
    	// Цикл, пока не вводится ctrl C (0x03) 
    	while (cBuffer_in[0] != 0x03){
    		// Повторное считывание любых последовательных данных и вывод
    		  if (ReadFile(hSerial, cBuffer_in, 1, cBytes_in, NULL)){
    			if (cBytes_in == 0) break;
    		// Вывод данных повторного считывания 
    			printf("%s",cBuffer_in, cBytes_in);
    		// Echo-передача символов назад отправителю
    if (!WriteFile(hSerial, cBuffer_in, cBytes_in,               
      cBytes_out, NULL)){
    				printf("\rfile write errors\n");
    				Sleep(4000);
    				return 0;
    				}
    		  }
    		
    	}
    		// Закрываем файл 
    	CloseHandle(hSerial);
        return 1;
    }

    После сборки новое ядро загружается и программа KOM_Tester выполняется для тестирования драйвера KOM_Port.DLL. Соединяются последовательные кабели и на системе разработки запускается HyperTerminal. Текстовый вывод на eBox и системе разработки должен совпадать, как и в предыдущих примерах программ с последовательным портом. В отладочной сборке сообщения отладчика в окне вывода будут указывать, когда вызываются различные точки входа KOM_XXXX.

    Следующие отладочные сообщения драйвера были выведены в окне вывода отладки во время выполнения KOM_Tester:

    Run Programs s KOM_Tester
    PB Debugger Loaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\OSDESIGN7\ 
     RELDIR\ ICOP_VORTEX86_60A_X86_RELEASE\KOM_TESTER.EXE'
    s KOM_Tester  22:33:23 11/25/2006 Eastern Standard Time
    End s KOM_Tester  22:33:23 11/25/2006 Eastern Standard Time
    
    PB Debugger Loaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\OSDESIGN7\ 
     RELDIR\ ICOP_VORTEX86_60A_X86_RELEASE\CONSOLE.DLL'
      61200 PID:400002 TID:4880012 DemoDriver - KOM_Open
      61200 PID:400002 TID:4880012 hDeviceContext - 
      61200 PID:400002 TID:4880012 4660
      61201 PID:400002 TID:4880012 
      61202 PID:400002 TID:4880012 DemoDriver - Exit KOM_Open
      61203 PID:400002 TID:4880012 KOM_DRIVER - KOM_Write
      61203 PID:400002 TID:4880012 hOpenContext - 
      61204 PID:400002 TID:4880012 22136
      61204 PID:400002 TID:4880012 
      61317 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Write
      61318 PID:400002 TID:4880012 KOM_DRIVER - KOM_Read
      61318 PID:400002 TID:4880012 hOpenContext - 
      61319 PID:400002 TID:4880012 22136
      61319 PID:400002 TID:4880012 
      69052 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Read
      69066 PID:400002 TID:4880012 KOM_DRIVER - KOM_Write
      69067 PID:400002 TID:4880012 hOpenContext - 
      69067 PID:400002 TID:4880012 22136
      69067 PID:400002 TID:4880012 
      69067 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Write
      69068 PID:400002 TID:4880012 KOM_DRIVER - KOM_Read
      69068 PID:400002 TID:4880012 hOpenContext - 
      69069 PID:400002 TID:4880012 22136
      69069 PID:400002 TID:4880012 
      69221 PID:400002 TID:4e80012 KOM_DRIVER - DLL_THREAD_DETACH
    .
    .
    .
    185145 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Read
    185159 PID:400002 TID:4880012 KOM_DRIVER - KOM_Close
    185160 PID:400002 TID:4880012 hOpenContext - 
    185160 PID:400002 TID:4880012 22136
    185162 PID:400002 TID:4880012 
    185162 PID:400002 TID:4880012 KOM_DRIVER - Exit KOM_Close
    PB Debugger Unloaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\ 
        OSDESIGN7\RELDIR\ICOP_VORTEX86_60A_X86_RELEASE\CONSOLE.DLL'
    PB Debugger Unloaded symbols for 'C:\WINCE600\OSDESIGNS\OSDESIGN7\  
        OSDESIGN7\ RELDIR\ICOP_VORTEX86_60A_X86_RELEASE\KOM_TESTER.EXE'
    206855 PID:400002 TID:40b0002 KOM_DRIVER - DLL_THREAD_DETACH

    Как можно видеть из вывода отладки, была загружена тестовая программа. Во время выполнения она вызвала KOM_Open. Было сделано несколько вызовов KOM_Write и KOM_Read для пересылки введенных данных на последовательный порт, соединенный с Hyperterminal, выполняющимся на ПК системы разработки. Прежде чем программа закончится после получения символа ввода CTL+C в последней операции KOM_Read, она делает вызов KOM_Close. Также во время процесса начальной загрузки вызывается KOM_init.

    Драйверы производственного качества

    Для реального драйвера производственного качества, такого как поставляется вместе с ОС, понадобиться сделать еще большее число усовершенствований в примере простого драйвера KOM. Он должен разрешать только один вызов KOM_OPEN в данный момент времени, чтобы два приложения не получили одновременно доступ к последовательному порту. Должно быть разработано полное множество функций IOCTL для поддержки настройки различных значений скорости в бодах, битов данных, битов контроля четности, стоп битов, и режимов квитирования. Он должен проверять ошибки переполнения, формирования кадра, и контроля четности, сообщаемые UART. Для более высоких скоростей передачи он может быть полностью буферизирован и управляться прерываниями. Программные циклы ошибок задержки должны рассматриваться для любого аппаратного отказа, который будут вызывать зависание драйвера. Вызов KOM_CLOSE, за которым сразу следует KOM_OPEN с другой скоростью в бодах, может вызывать ошибки, так как несколько символов буферизуются внутри UART. Функция KOM_CLOSE или KOM_OPEN может ожидать, пока буферы UART станут пустыми, чтобы разрешить эту проблему. Многие вызовы API имеют также параметры, так что они могут быть заданы для синхронной работы (ожидать, пока завершится операция, чтобы вернуть управление), или асинхронной (возвращать управление до завершения операции). Менеджер ресурсов В/В должен быть информирован о портах В/В, используемых драйвером.

    Если устройство имеет несколько портов KOM, его можно преобразовать в многослойный драйвер устройства (слои MDD и PDD). Запись реестра нужна для каждого экземпляра порта KOM с отличным индексом массива устройств, и базовый адрес В/В, который будет использовать драйвер, также должен быть добавлен, как новый ключ реестра. Наконец, все эти новые свойства, должно быть хорошо прокомментированы, полностью документированы, и тщательно протестированы.

    Можно найти реальный исходный код драйвера последовательного устройства CE в нескольких модулях в основном каталоге общего исходного кода драйвера \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL.

    Библиотека DLL высоко-скоростного дополнительного драйвера последовательного порта, управляемого прерываниями доступна в файле \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL\ISR16550.dll. ISR16550.dll, минимизирует время сигнала потока службы прерываний (IST). Это позволяет выполнять более быструю передачу данных, так как существует некоторый штраф за планирование системой передачи данных IST.ISR16550.dll из аппаратного буфера UART в программный буфер получения (FIFO) типа первый вошел, первый вышел, и заполняет данные FIFO передачи оборудования UART из буфера FIFO программной передачи без какого-либо вмешательства IST. IST будет сигнализировать, только когда получающий буфер достигнет своего порогового значения, а буфер передачи будет пустым. IST сигнализирует также, когда входящий поток данных прерывается.

    Дополнительная информация

  • Оперативная справочная система в Visual Studio, предоставляемая Windows Embedded CE 6.0, содержит дополнительную информацию по разработке драйверов устройств. Исходный код широкого ассортимента связанных с ПК драйверов устройств В/В поставляется вместе с CE в каталоге WINCE600\PUBLIC\COMMON\OAK\DRIVERS
  • Общедоступные общественные проекты исходного кода Phidgets и Logitech Web-камеры являются прекрасным источником информации по драйверам USB. Адресом URL общедоступных общественных проектов исходного кода CE является: http://msdn2.microsoft.com/en-us/embedded/aa731151.aspx
  • Пример драйвера потокового интерфейса для CE 5.0 разработан и описан в статье, "SPOT the Geek and Windows CE Drivers" Майка Холла (Mike Hall) по адресу http://msdn2.microsoft.com/en-us/library/aa459176.aspx
  • Лабораторные упражнения

  • Запустите драйвер KOM и выполните пример тестовой программы драйвера KOM и воспроизведите результаты, описанные в тексте.
  • Модифицируйте драйвер KOM, так чтобы только один процесс мог успешно его открыть в данный момент времени. Второе открытие должно возвращать код ошибки. Разработайте новую тестовую программу, и проверьте, что это работает.
  • Добавьте свойства IOCTRL в драйвер KOM, которые позволят задавать скорость в бодах, контроль четности, и число стоп-битов. Разработайте новую тестовую программу и проверьте, что это работает.
  • Разработайте тестовую программу, которая изменяет скорость в бодах во время отправки постоянных структур данных. Если имеются какие-либо проблемы, такие как недопустимые или пропущенные символы, модифицируйте код, чтобы их разрешить.
  • Сконфигурируйте данные менеджера ресурсов В/В, так чтобы он знал о диапазоне адресов В/В, используемом драйвером KOM. Попытайтесь открыть оба порта KOM и COM, и посмотрите, не будут ли возникать ошибки открытия в связи с конфликтом адресов В/В.
  • Изучите исходный код примера управляемого прерываниями драйвера последовательного устройства 16550 и модифицируйте драйвер KOM, чтобы сделать его управляемым прерываниями.
  • Вернуться к учебному плану