Введение в драйверы устройств ввода/вывода
Операции DMA в драйверах устройств
Прямой доступ к памяти (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 или шиной 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; }9.2.