Эта телефонная система LG Electronics Voice over IP (VoIP) основывается на Windows Embedded CE. Фотография с разрешения Mike Hall.
Драйверы устройств помогают создать слой абстракции между оборудованием и прикладными программами пользователя. Типичный пользователь, разрабатывающий приложение, не должен понимать низко-уровневые детали оборудования каждого интерфейса. Вместо этого прикладная программа вызывает API ОС, который вызывает драйвер устройства. Работая на более высоком уровне абстракции, используя ОС и драйверы устройств, программисты приложений могут быть более продуктивными. При использовании ОС и драйверов устройств приложения можно переносить на различные аппаратные платформы с меньшими усилиями, и система будет также более стабильной и безопасной. ОС является критически важной в планировании, размещении, и управлении устройствами, которые совместно используются несколькими процессами. В качестве примера представьте, что произойдет, если два приложения начнут одновременно посылать символьные данные непосредственно оборудованию принтера. В результате получится случайная смесь символов на каждой напечатанной странице.
Большинство распространенных драйверов устройств поставляются вместе с ОС или доступны производителям микросхем, но многие нужно будет переносить на новую аппаратную платформу, так как даже небольшие изменения, такие как задание адреса порта В/В или уровня прерывания могут требовать небольших модификаций кода драйвера.
Одной из проблем, постоянно встречающейся разработчикам встроенных систем, является необходимость разработки драйверов устройств для уникального оборудования, вводимого при проектировании каждой новой платы. Документация по интерфейсам драйверов и примеры драйверов устройств поставляются с каждой ОС. Детали реализации драйверов варьируются от ОС к ОС. Может также поставляться инструмент типа мастера, помогающий сгенерировать шаблонный код, необходимый в новом драйвере устройства для правильного взаимодействия с ОС. В некоторых случаях драйверы устройств покупаются даже у сторонних разработчиков или разрабатываются внешними консультантами. Написаны целые книги по теме написания драйверов устройств. Библиотеки драйверов устройств CE перечислены в таблице 9.1.
| Библиотека | Описание |
|---|---|
| Собственные библиотеки микропроцессора | Драйверы устройств для тесной интеграции собственных периферийных устройств микропроцессора. Например, микропроцессор ARM, и его дополнительная микросхема интегрирует многие периферийные устройства микропроцессора, такие как ..\WINCE600 \Platform\Common\Src\. |
| Специфические для микропроцессора библиотеки поддержки OAL | Функции OAL для устройств, таких как часы реального времени, таймер, и контроллер отладки Ethernet, который обычно присутствует в микропроцессоре. Эта библиотека минимизирует написанный код OAL. Они также называются драйверами ..\WINCE600 \Platform\Common\Src\. |
Драйверы устройств для периферии, которые являются специфическими для заданного SDB или аппаратной платформы. Эти драйверы имеют специфический для аппаратной платформы код и могут использоваться только на этой аппаратной платформе. Эти драйверы находятся в ..\WINCE600\Platform\<. |
|
| Другие распространенные драйверы периферийных устройств | Драйверы для стратегических периферийных наборов микросхем, которые обычно встречаются во многих SDB или конструкциях аппаратных платформ. Сюда входят такие устройства как Realtek RTL8139, базовый NE2000, наборы микросхем DEC/Intel 2114x Ethernet, MediaQ MQ200, наборы микросхем дисплея .\WINCE600\Public\Common\Oak\Drivers. Исходный код для модулей находится в каталоге Public. |
Потоковый интерфейс подходит для любого устройства В/В, которое можно логически считать источником или стоком данных. То есть, любое периферийное устройство, которое создает или потребляет потоки данных как свою основную функцию, является хорошим кандидатом для предоставления потокового интерфейса. Примером является устройство потокового порта. Примером устройства, которое не создает и не потребляет данные в традиционном смысле, будет устройство дисплея, и действительно, для управления оборудованием дисплея потоковый интерфейс не предоставляется.
Потоковый интерфейс может использовать другой нижележащий драйвер устройства для доступа к физическим периферийным устройствам, которыми управляет драйвер, или он может обращаться к устройству непосредственно, если устройство отражено в память. Драйверы аудио-устройств для встроенного аудио-оборудования являются примером прямого доступа.
Сами функции потокового интерфейса создаются таким образом, чтобы как можно ближе соответствовать семантике обычных интерфейсов прикладного программирования (API) файловых систем, таких как CreateFile, WriteFile, , IOControl, и CloseHandle. В качестве побочного эффекта такого дизайна устройства, которые управляются потоковым интерфейсом, доступны для приложений через файловую систему; приложения взаимодействуют с драйвером, открывая специальные файлы в файловой системе.
Драйверы потокового интерфейса могут иметь монолитную архитектуру или архитектуру с двумя слоями, MDD и
(рис 9.1) Две альтернативные архитектуры драйвера потокового интерфейса. Монолитный драйвер потокового интерфейса или Разделенный на слои драйвер потокового интерфейса. В разделенной на слои архитектуре драйвера в CE два слоя называются MDD и PDD
Интерпретация устройств как специальных файлов распространена во многих операционных системах, включая настольные версии Microsoft Windows и даже первые версия Unix. Там устройства печати традиционно представлялись именами специальных файлов LPTx:, а последовательные порты именами специальных файлов COMx:, и т.д.
Несмотря на базовые характеристики потокового интерфейса, его можно реализовать различным образом. Например, даже хотя потоковый интерфейс обычно создается для периферийных устройств независимыми поставщиками оборудования (
В некоторых менее распространенных случаях драйверы потокового интерфейса могут переупаковывать существующие ресурсы, обычно таким образом, который специфические приложения смогут более легко использовать. Например, такой тип драйвера потокового интерфейса может управлять приемником системы глобального позиционирования (GPS) с последовательным интерфейсом. В этом примере
Однако приемник GPS может предоставлять данные позиционирования в неудобном формате, или создатель приложения может захотеть сохранить скрытыми детали управления специальными моделями приемников GPS. Поэтому можно написать драйвер потокового интерфейса для обеспечения взаимодействия между приложением и приемником GPS. Драйвер будет взаимодействовать с приемником GPS как и раньше через специальный файл COMx:, но может переупаковывать данные позиционирования в более удобном формате для приложения. Драйвер может предоставлять свои собственные службы как специальный файл GPSx:, который будет открывать приложение, чтобы прочитать данные позиционирования.
Драйвер потокового интерфейса получает команды от
Все драйверы потоковых интерфейсов, управляют ли они встроенными устройствами или устанавливаемыми устройствами, загружаются ли они во время начальной загрузки или загружаются динамически, имеют похожие взаимодействия с другими системными компонентами. Рисунок 9.2 показывает архитектуру драйверов потокового интерфейса для встроенных устройств, которые загружаются
Различные вопросы могут влиять на конструктивные решения во время реализации динамически подключаемой библиотеки (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 |
Эта функция де-инициализирует устройство. Ее вызывает ActivateDeviceEx, ActivateDevice, или RegisterDevice. |
XXX_Init |
Эта функция инициализирует устройство. Ее вызывает 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 вызывающему приложению, чтобы указать на отказ.
Большинство периферийных устройств могут генерировать прерывания, чтобы получить обслуживание от операционной системы. Некоторыми примерами устройств, которые могут использовать прерывания, являются платы ПК, встроенные таймеры, устройства аудио-ввода, клавиатуры, сенсорные экраны, последовательные порты, и устройства указатели. Почти любой тип периферийного устройства может использовать прерывания в качестве основного метода инициирования действий ОС по обслуживанию.
Так как эти периферийные устройства могут вызывать или сигнализировать о прерываниях, их драйверы устройств должны обрабатывать прерывания, чтобы обслужить свои устройства. Физические прерывания (IRQ) являются аппаратными линиями связи, по которым устройства могут посылать сигналы прерываний в микропроцессор. Логические прерывания (SYSINTR) являются отображением IRQ, которое определяет OAL.
Когда обрабатывается прерывание, имеет место определенная последовательность событий. Необходимо написать для драйвера устройства запрос на обслуживание прерывания (
InterruptDone, которая в свою очередь вызывает в OAL функцию OEMInterruptDone.Функция OEMInterruptDone снова включает текущее прерывание. Драйвер устройства должен выполнить при загрузке следующие действия:
SYSINTR в функции OEMInit из OAL. -или- Драйвер шины, который загружает драйвер, должен отобразить IRQ в SYSINTR, что делается в случае драйвера шины PCI.OEMInit.Процесс регистрации обработчика прерываний регистрирует событие, которое ассоциируется с системным прерыванием SYSINTR. После загрузки драйвера устройства драйвер создает поток службы прерываний (InterruptInitialize для регистрации события. WaitForSingleObject для ожидания этого события и регистрации его в обработчике прерываний. SYSINTR ).
Если для определенного драйвера используется реализация компании Microsoft модельного драйвера устройства (MDD), вам не нужно будет писать код для регистрации прерывания. Слой MDD драйвера регистрирует драйвер для прерываний. Если создается монолитный драйвер, нужно реализовать код для регистрации CreateEvent для создания события и функция InterruptInitialize для соединения события с SYSINTR.
Если драйвер устройства должен остановить обработку прерывания, то драйвер может использовать функцию InterruptDisable. Когда драйвер вызывает эту функцию, обработчик прерываний удаляет ассоциацию между OEMInterruptDisable для отключения прерывания. Драйвер, если понадобиться, может позже снова зарегистрироваться для прерывания.
Могут возникать ситуации, когда требуется передать информацию между процедурой службы прерываний (
SYSINTR_NOP, пока буфер не будет заполнен, а затем вернет соответствующий идентификатор SYSINTR, когда
Для передачи данных между
Config.bib . Config.bib содержит несколько примеров резервирования физической памяти для последовательного и отладочного драйверов.Вызовите функцию MmMapIoSpace в
Функция MMMapIoSpace вызывает функции VirtualAlloc и VirtualCopy для отображения физической памяти в адреса виртуальной памяти, к которой может обращаться
Можно также вызывать функции VirtualAlloc и VirtualCopy непосредственно. Например, можно выделить память вне пространства виртуальной памяти процесса, вызывая VirtualAlloc с параметрами, заданными следующим образом:
dwSize >= 2 MBflAllocationType задается как MEM_RESERVEflProtect задается как PAGE_NOACCESSВ Windows Embedded CE устанавливаемая Config.. Поток службы прерываний (SetKMode(TRUE), чтобы позволить GWES писать в общую кучу памяти.
Чтобы ОС пробудила CreateEvent для создания объекта события. После обработки прерывания
Когда происходит аппаратное прерывание, ядро сигнализирует о событии от имени
Обычно потоки CeSetThreadPriority. Таблица 9.3 показывает функции, которые обычно использует
| Функция | Описание |
|---|---|
InterruptInitialize |
Соединяет событие с идентификатором прерывания |
WaitForSingleObject |
Возвращает управление, когда указанный объект находится в сигнальном состоянии или когда истекает интервал перерыва. |
InterruptDone |
Инструктирует ядро о повторном включении аппаратного прерывания, связанного с этим потоком. |
Следующий список содержит примеры того, что
CreateEvent в качестве триггера SysIntr из реестра и позволить OAL отобразить IRQ в значение SYSINTR перед загрузкой драйвера.Следующий пример кода драйвера устройства клавиатуры PS/2 из ..\WINCE600\Public\Common\OAK\Drivers\Keybd\Ps2_8042\Ps2keybd.cpp показывает типичную
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 к общему буферу и вразброс/слитно с помощью CEDDK.dll или функций ядра. DMA к общему буферу использует непрерывный буфер в основной памяти. DMA вразброс/слитно использует множество блоков с различными адресами памяти.
Стандартный перенос DMA происходит, когда контроллер DMA выполняет пересылку. Передача контроллера шины DMA происходит, когда периферийное устройство выполняет передачу. Компания Microsoft рекомендует использовать для DMA функции CEDDK.dll. Функции CEDDK.dll вызывают функции ядра. Таблица 9.4 сравнивает два способа выполнения DMA, с помощью функций CEDDK.dll и с помощью функций ядра.
| Использование функций 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 или CEDDK.dll транслируют физический адрес RAM в соответствующий физический адрес относительно шины для контроллера DMA. Для настройки общего буфера для DMA контроллера шины, используя функции CEDDK.dll, драйвер устройства DMA контроллера шины может вызвать функцию HalAllocateCommonBuffer со структурой DMA_ADAPTER_OBJECT.
Следующий пример кода показывает вызов функции HalAllocateCommonBuffer. Пример кода извлечен из драйвера ES1371, расположенного в каталоге . .\WINCE600\Public\ Common\OAK\Drivers\ WaveDev\.
// Размещаем в стеке объект адаптера
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 вразброс/слитно, необходимо использовать одновременно несколько пар базовых адресов и длин. Драйвер ..\WINCE600\Public\Common\Oak\Drivers\Block\ является примером реализации DMA вразброс/слитно.
В качестве учебного примера простого драйвера потокового интерфейса мы разработаем теперь новый драйвер устройства для последовательного порта eBox. Он будет основываться на предыдущем примере программы C/C++ последовательного порта, который взаимодействует непосредственно с оборудованием последовательного порта. Вспомните, что она использует функции CEDDK.lib, которые пишут в и читают непосредственно порты В/В, соединенные с 16550
Наш драйвер порта KOM должен быть задан как подпроект DLL (а не приложение), и он должен определять и экспортировать стандартные функции потокового интерфейса. Управление 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;
}
Вспомните, что
[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 нового драйвера также необходимо включить в образ ядра времени выполнения. Чтобы включить новый файл в ядро, откройте файл *. подпроекта драйвера и подтвердите/добавьте следующую запись:
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 для поддержки настройки различных значений скорости в бодах, битов данных, битов контроля четности, стоп битов, и режимов квитирования. Он должен проверять ошибки переполнения, формирования кадра, и контроля четности, сообщаемые KOM_CLOSE, за которым сразу следует KOM_OPEN с другой скоростью в бодах, может вызывать ошибки, так как несколько символов буферизуются внутри KOM_CLOSE или KOM_OPEN может ожидать, пока буферы
Если устройство имеет несколько портов KOM, его можно преобразовать в многослойный драйвер устройства (слои MDD и
Можно найти реальный исходный код драйвера последовательного устройства CE в нескольких модулях в основном каталоге общего исходного кода драйвера \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL.
Библиотека DLL высоко-скоростного дополнительного драйвера последовательного порта, управляемого прерываниями доступна в файле \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL\ISR16550.dll. ISR16550.dll, минимизирует время сигнала потока службы прерываний (
WINCE600\PUBLIC\COMMON\OAK\DRIVERS
Эта телефонная система LG Electronics Voice over IP (VoIP) основывается на Windows Embedded CE. Фотография с разрешения Mike Hall.
Драйверы устройств помогают создать слой абстракции между оборудованием и прикладными программами пользователя. Типичный пользователь, разрабатывающий приложение, не должен понимать низко-уровневые детали оборудования каждого интерфейса. Вместо этого прикладная программа вызывает API ОС, который вызывает драйвер устройства. Работая на более высоком уровне абстракции, используя ОС и драйверы устройств, программисты приложений могут быть более продуктивными. При использовании ОС и драйверов устройств приложения можно переносить на различные аппаратные платформы с меньшими усилиями, и система будет также более стабильной и безопасной. ОС является критически важной в планировании, размещении, и управлении устройствами, которые совместно используются несколькими процессами. В качестве примера представьте, что произойдет, если два приложения начнут одновременно посылать символьные данные непосредственно оборудованию принтера. В результате получится случайная смесь символов на каждой напечатанной странице.
Большинство распространенных драйверов устройств поставляются вместе с ОС или доступны производителям микросхем, но многие нужно будет переносить на новую аппаратную платформу, так как даже небольшие изменения, такие как задание адреса порта В/В или уровня прерывания могут требовать небольших модификаций кода драйвера.
Одной из проблем, постоянно встречающейся разработчикам встроенных систем, является необходимость разработки драйверов устройств для уникального оборудования, вводимого при проектировании каждой новой платы. Документация по интерфейсам драйверов и примеры драйверов устройств поставляются с каждой ОС. Детали реализации драйверов варьируются от ОС к ОС. Может также поставляться инструмент типа мастера, помогающий сгенерировать шаблонный код, необходимый в новом драйвере устройства для правильного взаимодействия с ОС. В некоторых случаях драйверы устройств покупаются даже у сторонних разработчиков или разрабатываются внешними консультантами. Написаны целые книги по теме написания драйверов устройств. Библиотеки драйверов устройств CE перечислены в таблице 9.1.
| Библиотека | Описание |
|---|---|
| Собственные библиотеки микропроцессора | Драйверы устройств для тесной интеграции собственных периферийных устройств микропроцессора. Например, микропроцессор ARM, и его дополнительная микросхема интегрирует многие периферийные устройства микропроцессора, такие как ..\WINCE600 \Platform\Common\Src\. |
| Специфические для микропроцессора библиотеки поддержки OAL | Функции OAL для устройств, таких как часы реального времени, таймер, и контроллер отладки Ethernet, который обычно присутствует в микропроцессоре. Эта библиотека минимизирует написанный код OAL. Они также называются драйверами ..\WINCE600 \Platform\Common\Src\. |
Драйверы устройств для периферии, которые являются специфическими для заданного SDB или аппаратной платформы. Эти драйверы имеют специфический для аппаратной платформы код и могут использоваться только на этой аппаратной платформе. Эти драйверы находятся в ..\WINCE600\Platform\<. |
|
| Другие распространенные драйверы периферийных устройств | Драйверы для стратегических периферийных наборов микросхем, которые обычно встречаются во многих SDB или конструкциях аппаратных платформ. Сюда входят такие устройства как Realtek RTL8139, базовый NE2000, наборы микросхем DEC/Intel 2114x Ethernet, MediaQ MQ200, наборы микросхем дисплея .\WINCE600\Public\Common\Oak\Drivers. Исходный код для модулей находится в каталоге Public. |
Потоковый интерфейс подходит для любого устройства В/В, которое можно логически считать источником или стоком данных. То есть, любое периферийное устройство, которое создает или потребляет потоки данных как свою основную функцию, является хорошим кандидатом для предоставления потокового интерфейса. Примером является устройство потокового порта. Примером устройства, которое не создает и не потребляет данные в традиционном смысле, будет устройство дисплея, и действительно, для управления оборудованием дисплея потоковый интерфейс не предоставляется.
Потоковый интерфейс может использовать другой нижележащий драйвер устройства для доступа к физическим периферийным устройствам, которыми управляет драйвер, или он может обращаться к устройству непосредственно, если устройство отражено в память. Драйверы аудио-устройств для встроенного аудио-оборудования являются примером прямого доступа.
Сами функции потокового интерфейса создаются таким образом, чтобы как можно ближе соответствовать семантике обычных интерфейсов прикладного программирования (API) файловых систем, таких как CreateFile, WriteFile, , IOControl, и CloseHandle. В качестве побочного эффекта такого дизайна устройства, которые управляются потоковым интерфейсом, доступны для приложений через файловую систему; приложения взаимодействуют с драйвером, открывая специальные файлы в файловой системе.
Драйверы потокового интерфейса могут иметь монолитную архитектуру или архитектуру с двумя слоями, MDD и
(рис 9.1) Две альтернативные архитектуры драйвера потокового интерфейса. Монолитный драйвер потокового интерфейса или Разделенный на слои драйвер потокового интерфейса. В разделенной на слои архитектуре драйвера в CE два слоя называются MDD и PDD
Интерпретация устройств как специальных файлов распространена во многих операционных системах, включая настольные версии Microsoft Windows и даже первые версия Unix. Там устройства печати традиционно представлялись именами специальных файлов LPTx:, а последовательные порты именами специальных файлов COMx:, и т.д.
Несмотря на базовые характеристики потокового интерфейса, его можно реализовать различным образом. Например, даже хотя потоковый интерфейс обычно создается для периферийных устройств независимыми поставщиками оборудования (
В некоторых менее распространенных случаях драйверы потокового интерфейса могут переупаковывать существующие ресурсы, обычно таким образом, который специфические приложения смогут более легко использовать. Например, такой тип драйвера потокового интерфейса может управлять приемником системы глобального позиционирования (GPS) с последовательным интерфейсом. В этом примере
Однако приемник GPS может предоставлять данные позиционирования в неудобном формате, или создатель приложения может захотеть сохранить скрытыми детали управления специальными моделями приемников GPS. Поэтому можно написать драйвер потокового интерфейса для обеспечения взаимодействия между приложением и приемником GPS. Драйвер будет взаимодействовать с приемником GPS как и раньше через специальный файл COMx:, но может переупаковывать данные позиционирования в более удобном формате для приложения. Драйвер может предоставлять свои собственные службы как специальный файл GPSx:, который будет открывать приложение, чтобы прочитать данные позиционирования.
Драйвер потокового интерфейса получает команды от
Все драйверы потоковых интерфейсов, управляют ли они встроенными устройствами или устанавливаемыми устройствами, загружаются ли они во время начальной загрузки или загружаются динамически, имеют похожие взаимодействия с другими системными компонентами. Рисунок 9.2 показывает архитектуру драйверов потокового интерфейса для встроенных устройств, которые загружаются
Различные вопросы могут влиять на конструктивные решения во время реализации динамически подключаемой библиотеки (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 |
Эта функция де-инициализирует устройство. Ее вызывает ActivateDeviceEx, ActivateDevice, или RegisterDevice. |
XXX_Init |
Эта функция инициализирует устройство. Ее вызывает 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 вызывающему приложению, чтобы указать на отказ.
Большинство периферийных устройств могут генерировать прерывания, чтобы получить обслуживание от операционной системы. Некоторыми примерами устройств, которые могут использовать прерывания, являются платы ПК, встроенные таймеры, устройства аудио-ввода, клавиатуры, сенсорные экраны, последовательные порты, и устройства указатели. Почти любой тип периферийного устройства может использовать прерывания в качестве основного метода инициирования действий ОС по обслуживанию.
Так как эти периферийные устройства могут вызывать или сигнализировать о прерываниях, их драйверы устройств должны обрабатывать прерывания, чтобы обслужить свои устройства. Физические прерывания (IRQ) являются аппаратными линиями связи, по которым устройства могут посылать сигналы прерываний в микропроцессор. Логические прерывания (SYSINTR) являются отображением IRQ, которое определяет OAL.
Когда обрабатывается прерывание, имеет место определенная последовательность событий. Необходимо написать для драйвера устройства запрос на обслуживание прерывания (
InterruptDone, которая в свою очередь вызывает в OAL функцию OEMInterruptDone.Функция OEMInterruptDone снова включает текущее прерывание. Драйвер устройства должен выполнить при загрузке следующие действия:
SYSINTR в функции OEMInit из OAL. -или- Драйвер шины, который загружает драйвер, должен отобразить IRQ в SYSINTR, что делается в случае драйвера шины PCI.OEMInit.Процесс регистрации обработчика прерываний регистрирует событие, которое ассоциируется с системным прерыванием SYSINTR. После загрузки драйвера устройства драйвер создает поток службы прерываний (InterruptInitialize для регистрации события. WaitForSingleObject для ожидания этого события и регистрации его в обработчике прерываний. SYSINTR ).
Если для определенного драйвера используется реализация компании Microsoft модельного драйвера устройства (MDD), вам не нужно будет писать код для регистрации прерывания. Слой MDD драйвера регистрирует драйвер для прерываний. Если создается монолитный драйвер, нужно реализовать код для регистрации CreateEvent для создания события и функция InterruptInitialize для соединения события с SYSINTR.
Если драйвер устройства должен остановить обработку прерывания, то драйвер может использовать функцию InterruptDisable. Когда драйвер вызывает эту функцию, обработчик прерываний удаляет ассоциацию между OEMInterruptDisable для отключения прерывания. Драйвер, если понадобиться, может позже снова зарегистрироваться для прерывания.
Могут возникать ситуации, когда требуется передать информацию между процедурой службы прерываний (
SYSINTR_NOP, пока буфер не будет заполнен, а затем вернет соответствующий идентификатор SYSINTR, когда
Для передачи данных между
Config.bib . Config.bib содержит несколько примеров резервирования физической памяти для последовательного и отладочного драйверов.Вызовите функцию MmMapIoSpace в
Функция MMMapIoSpace вызывает функции VirtualAlloc и VirtualCopy для отображения физической памяти в адреса виртуальной памяти, к которой может обращаться
Можно также вызывать функции VirtualAlloc и VirtualCopy непосредственно. Например, можно выделить память вне пространства виртуальной памяти процесса, вызывая VirtualAlloc с параметрами, заданными следующим образом:
dwSize >= 2 MBflAllocationType задается как MEM_RESERVEflProtect задается как PAGE_NOACCESSВ Windows Embedded CE устанавливаемая Config.. Поток службы прерываний (SetKMode(TRUE), чтобы позволить GWES писать в общую кучу памяти.
Чтобы ОС пробудила CreateEvent для создания объекта события. После обработки прерывания
Когда происходит аппаратное прерывание, ядро сигнализирует о событии от имени
Обычно потоки CeSetThreadPriority. Таблица 9.3 показывает функции, которые обычно использует
| Функция | Описание |
|---|---|
InterruptInitialize |
Соединяет событие с идентификатором прерывания |
WaitForSingleObject |
Возвращает управление, когда указанный объект находится в сигнальном состоянии или когда истекает интервал перерыва. |
InterruptDone |
Инструктирует ядро о повторном включении аппаратного прерывания, связанного с этим потоком. |
Следующий список содержит примеры того, что
CreateEvent в качестве триггера SysIntr из реестра и позволить OAL отобразить IRQ в значение SYSINTR перед загрузкой драйвера.Следующий пример кода драйвера устройства клавиатуры PS/2 из ..\WINCE600\Public\Common\OAK\Drivers\Keybd\Ps2_8042\Ps2keybd.cpp показывает типичную
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 к общему буферу и вразброс/слитно с помощью CEDDK.dll или функций ядра. DMA к общему буферу использует непрерывный буфер в основной памяти. DMA вразброс/слитно использует множество блоков с различными адресами памяти.
Стандартный перенос DMA происходит, когда контроллер DMA выполняет пересылку. Передача контроллера шины DMA происходит, когда периферийное устройство выполняет передачу. Компания Microsoft рекомендует использовать для DMA функции CEDDK.dll. Функции CEDDK.dll вызывают функции ядра. Таблица 9.4 сравнивает два способа выполнения DMA, с помощью функций CEDDK.dll и с помощью функций ядра.
| Использование функций 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 или CEDDK.dll транслируют физический адрес RAM в соответствующий физический адрес относительно шины для контроллера DMA. Для настройки общего буфера для DMA контроллера шины, используя функции CEDDK.dll, драйвер устройства DMA контроллера шины может вызвать функцию HalAllocateCommonBuffer со структурой DMA_ADAPTER_OBJECT.
Следующий пример кода показывает вызов функции HalAllocateCommonBuffer. Пример кода извлечен из драйвера ES1371, расположенного в каталоге . .\WINCE600\Public\ Common\OAK\Drivers\ WaveDev\.
// Размещаем в стеке объект адаптера
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 вразброс/слитно, необходимо использовать одновременно несколько пар базовых адресов и длин. Драйвер ..\WINCE600\Public\Common\Oak\Drivers\Block\ является примером реализации DMA вразброс/слитно.
В качестве учебного примера простого драйвера потокового интерфейса мы разработаем теперь новый драйвер устройства для последовательного порта eBox. Он будет основываться на предыдущем примере программы C/C++ последовательного порта, который взаимодействует непосредственно с оборудованием последовательного порта. Вспомните, что она использует функции CEDDK.lib, которые пишут в и читают непосредственно порты В/В, соединенные с 16550
Наш драйвер порта KOM должен быть задан как подпроект DLL (а не приложение), и он должен определять и экспортировать стандартные функции потокового интерфейса. Управление 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;
}
Вспомните, что
[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 нового драйвера также необходимо включить в образ ядра времени выполнения. Чтобы включить новый файл в ядро, откройте файл *. подпроекта драйвера и подтвердите/добавьте следующую запись:
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 для поддержки настройки различных значений скорости в бодах, битов данных, битов контроля четности, стоп битов, и режимов квитирования. Он должен проверять ошибки переполнения, формирования кадра, и контроля четности, сообщаемые KOM_CLOSE, за которым сразу следует KOM_OPEN с другой скоростью в бодах, может вызывать ошибки, так как несколько символов буферизуются внутри KOM_CLOSE или KOM_OPEN может ожидать, пока буферы
Если устройство имеет несколько портов KOM, его можно преобразовать в многослойный драйвер устройства (слои MDD и
Можно найти реальный исходный код драйвера последовательного устройства CE в нескольких модулях в основном каталоге общего исходного кода драйвера \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL.
Библиотека DLL высоко-скоростного дополнительного драйвера последовательного порта, управляемого прерываниями доступна в файле \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL\ISR16550.dll. ISR16550.dll, минимизирует время сигнала потока службы прерываний (
WINCE600\PUBLIC\COMMON\OAK\DRIVERSДля получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.