ФГБОУ ВПО
«Воронежский государственный технический университет»
Кафедра автоматизированных и вычислительных систем
к выполнению лабораторной работы № 3
по дисциплине "Периферийные устройства"
для студентов специальности 230101
«Вычислительные машины, комплексы, системы и сети»
очной и очной сокращенной форм обучения
Воронеж 2012
Составители: канд. техн. наук А.М. Нужный,
канд. техн. наук Н.И. Гребенникова
УДК 681.3.06
Написание драйверов: методические указания к выполнению лабораторной работы № 3 по дисциплине "Периферийные устройства" для студентов специальности 230101 «Вычислительные машины, комплексы, системы и сети» очной и очной сокращенной форм обучения / ФГБОУ ВПО «Воронежский государственный технический университет»; сост. А.М. Нужный, Н.И. Гребенникова. Воронеж, 2012. 26 с.
В методических указаниях приводятся задания и теоретические сведения по темам лабораторных работ.
Предназначены для студентов специальности 230101, изучающих дисциплину "Периферийные устройства".
Методические указания подготовлены в электронном виде в текстовом редакторе MS WORD и содержатся в файле ПУ. Ч.2.doc.
Ил. 1. Библиогр.: 4 назв.
Рецензент д-р техн. наук, проф. О.Н. Чопоров
Ответственный за выпуск зав. кафедрой д-р техн. наук, проф. С.Л. Подвальный
Издается по решению редакционно-издательского совета Воронежского государственного технического университета
ФГБОУ ВПО "Воронежский государственный технический университет", 2012
СОДЕРЖАНИЕ
Лабораторная работа №3. |
2 |
3.1. Общие методические указания по выполнению лабораторной работы № 3 |
2 |
3.2. Выполнение работы |
3 |
Библиографический список |
26 |
Цель работы: изучение драйвера, предназначенного для работы с прерываниями и организации передачи данных с использованием прямого доступа устройства к памяти (Direct memory access, DMA).
Необходимое ПО: Для выполнения работы необходимо установить Oracle VirtualBox (инструкции по установке и настройке приведены в отдельном документе) с установленной на неё виртуальной машиной (ВМ) под управлением ОС Windows XP. Следующее ПО должно присутствовать на ВМ: Microsoft Visual Studio C++, DDK и DebugView. Для чтения/записи данных в буфер виртуального устройства применяется программа PuTTY.
Исходные данные для лабораторной работы:
Все необходимые файлы для компиляции и сборки драйвера находятся в папке PIO:
itit.cpp; - файл содержит код драйвера на языке С++;
driver.h – заголовочный файл, содержащий объявления, необходимые для компиляции драйвера;
MAKEFILE - управляет работой программы Build пакета DDK;
SOURCES - отражает индивидуальные настройки процесса компиляции и сборки;
Тестирующие программы находится в папке ExampleTest.
Порядок выполнения работы:
.Прочитать про механизмы передачи данных и регистры устройства.
Ознакомиться с описанием виртуального устройства.
Выполнить компиляцию и сборку драйвера средствами DDK.
Провести тестирование драйвера.
Предварительные Объявления и процедура DriverEntry
Программная часть драйвера начинается с обязательной функции с именем DriverEntry(), которая автоматически вызывается системой на этапе загрузки драйвера. Эта функция должна содержать все действия по его инициализации. В качестве первого параметра наша функция получает указатель на объект драйвера типа PDRIVER_OBJECT. При загрузке драйвера система создает объект драйвера (driver object), олицетворяющий образ драйвера в памяти. С другой стороны, объект драйвера представляет собой структуру, содержащую необходимые для функционирования драйвера данные и адреса функций. Второй параметр PUNICODE_STRING - раздел реестра (driver service key) в UNICODE.
Далее необходимо определить используемые в ней данные, в т.ч. указатель на device object и две символьные строки UNICODE_STRING с именами устройств. Системные программы взаимодействуют с объектом устройства, созданным драйвером, посредством указателя на него. Необходимо иметь в виду, что объект устройства должен иметь два имени, одно - в пространстве имен NT, другое - в пространстве имен Win32. Эти имена должны представлять собой структуры UNICODE_STRING. Имена объектов устройств составляются по определенным правилам. NT-имя предваряется префиксом Device, а Win32-имя -префиксом DosDevice.
Листинг 16.
//=================================================
// Файл init.cpp
//=================================================
#include "driver.h"
// Предварительные объявления функций
static NTSTATUS CreateDevice(
IN PDRIVER_OBJECT pDriverObject,
IN ULONG portBase,
IN ULONG Irq );
static NTSTATUS Create
(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
static NTSTATUS Close
(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
static VOID DriverUnload
(IN PDRIVER_OBJECT pDriverObject);
static NTSTATUS Write
(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
static NTSTATUS Read
(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
BOOLEAN Isr
(IN PKINTERRUPT pInterruptObject,
IN PVOID pServiceContext);
//=================================================
// Функция: DriverEntry
// Назначение: Инициализирует драйвер, подключает объект устройства для
// получения прерываний.
// Аргументы: pDriverObject - поступает от Диспетчера ввода/вывода
//pRegistryPath - указатель на Юникод-строку,
//обозначающую раздел Системного Реестра, созданный
//для данного драйвера.
// Возвращаемое значение:
//NTSTATUS - в случае нормального
//завершения STATUS_SUCCESS
// или код ошибки STATUS_Xxx
extern "C" NTSTATUS DriverEntry (IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
NTSTATUS status;
#if DBG
DbgPrint("IRQDMA: in DriverEntry, RegistryPath is:\n %ws. \n",
pRegistryPath->Buffer);
#endif
// Регистрируем рабочие процедуры драйвера:
pDriverObject->DriverUnload = DriverUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = Create;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Close;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = Write;
pDriverObject->MajorFunction[IRP_MJ_READ] = Read;
//Работа по созданию объекта устройства,подключению
//ресурсов, прерывания, созданию символьной ссылки:
status = CreateDevice(pDriverObject, 0xD040, 23);
return status;
}
//=================================================
// Функция: CreateDevice
// Назначение: Создание устройства с точки зрения //системы
// Аргументы: pDriverObject - поступает от //Диспетчера ввода/вывода
//portBase - адрес базового регистра параллельного //порта (378h)
//Irq - прерывание (в терминах шины ISA) для //обслуживания порта
// Возвращаемое значение:
// NTSTATUS - в случае нормального //завершения STATUS_SUCCESS
// или код ошибки STATUS_Xxx
NTSTATUS CreateDevice
(IN PDRIVER_OBJECT pDriverObject, IN ULONG portBase, IN ULONG Irq)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
// Создаем внутреннее имя устройства
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\IRQDMA");
// Создаем объект устройства
status= IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION),
&devName,
FILE_DEVICE_UNKNOWN,
0,
TRUE,
&pDevObj);
if (!NT_SUCCESS(status)) return status;
// Будем использовать метод буферизации BUFFERED_IO
pDevObj->Flags |= DO_BUFFERED_IO;
// Заполняем данными структуру Device Extension
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
// сохраняем - это пригодится
pDevExt->ustrDeviceName = devName;
pDevExt->Irq = Irq;
pDevExt->portBase = (PUCHAR)portBase;
//выполняем выделение физически непрерывной области //памятипод буферы приема/отправки
PVOID VirtualAddress;
PHYSICAL_ADDRESS maxAcceptableAddress;
maxAcceptableAddress.QuadPart = 0x000FFFFFULL;
pDevExt->deviceOutBuffer = MmAllocateContiguousMemory(MAX_BUFFER_SIZE, maxAcceptableAddress);
if (pDevExt->deviceOutBuffer == NULL)
{// При неудаче - удаляем объект устройства:
IoDeleteDevice(pDevObj);
#if DBG
DbgPrint("Can't get memory");
#endif
return status;
}
pDevExt->deviceInBuffer = MmAllocateContiguousMemory(MAX_BUFFER_SIZE, maxAcceptableAddress);
if (pDevExt->deviceInBuffer == NULL)
{// При неудаче - удаляем объект устройства:
IoDeleteDevice(pDevObj);
#if DBG
DbgPrint("Can't get memory");
#endif
return status;
}
pDevExt->OutBufferLength = 0;
// сейчас нет данных в буфере отправки
pDevExt->InBufferLength = 0;
// сейчас нет данных в буфере приема
// Создаем и подключаем объект прерываний:
KIRQL kIrql;
KAFFINITY kAffinity;
ULONG kVector =
HalGetInterruptVector(PCIBus, 0, pDevExt->Irq, pDevExt->Irq, &kIrql, &kAffinity);
// Замечание. Для PCI шины второй параметр //(номер шины) обычно
// равен 0, а третий и четвертый параметры равны.
#if DBG
DbgPrint("IRQDMA: Interrupt %d converted to kIrql = %d, "
"kAffinity = %d, kVector = %X(hex)\n",
pDevExt->Irq, kIrql, kAffinity, kVector);
#endif
status =
IoConnectInterrupt (&pDevExt->pIntObj,
// Здесь будет создан Interrupt Object
Isr, // Наша функция ISR
pDevExt, // Этот указатель ISR функция будет
// получать при вызове (контекстный указатель)
NULL, // Не будем использовать spin-блокировку для
// безопасного доступа к совместно используемым //данным
kVector, // транслированное значение прерывания
kIrql, // DIRQL
kIrql, // DIRQL
Latched, // Прерывание по перепаду
TRUE, // Совместно используемое (Shared) прерывание
kAffinity, // Поцессоров в мультипроцессорной //системе
FALSE); // Не сохранять значения регистров //сопроцессора
if (!NT_SUCCESS(status))
{
// В случае неудачи удаляем объект устройства
IoDeleteDevice(pDevObj);
return status;
}
#if DBG
DbgPrint("IRQDMA: Interrupt successfully connected.\n");
#endif
// Создаем символьную ссылку:
UNICODE_STRING symLinkName;
// Сформировать символьное имя:
// Для того, чтобы работало в Windows 98 & XP :
#define SYM_LINK_NAME L"\\DosDevices\\IRQDMA"
RtlInitUnicodeString(&symLinkName, SYM_LINK_NAME);
// Создать символьную ссылку:
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{ // При неудаче - отключаемся от //прерывания и
// удаляем объект устройства:
IoDisconnectInterrupt(pDevExt->pIntObj);
IoDeleteDevice(pDevObj);
return status;
}
pDevExt->ustrSymLinkName = symLinkName;
#if DBG
DbgPrint("IRQDMA: Symbolic Link is created: %ws. \n",
pDevExt->ustrSymLinkName.Buffer);
#endif
WRITE_PORT_ULONG((PULONG)(pDevExt->portBase + COMAND_REG), START_READ_CMD);
return STATUS_SUCCESS;
}
Рабочие процедуры обработки запросов read/write
Процедуры Read/Write предназначены для обработки запросов Диспетчера ввода/вывода, которые он формирует в виде IRP пакетов с кодами IRP_MJ_READ/IRP_MJ_WRITE по результатам обращения к драйверу из пользовательских приложений с вызовами read/write или из кода режима ядра с вызовами ZwReadFile или ZwWriteFile. В данном примере наша функция обработки запросов чтения/записи ничего полезного не делает, и ее регистрация выполнена только для демонстрации, как это могло бы быть в более "развитом" драйвере.
Листинг 17.
//=================================================
// Функция: Write
// Назначение: Обрабатывает запрос по поводу Win32 //вызова WriteFile
// Аргументы: pDevObj - поступает от Диспетчера //ввода/вывода
// pIrp - поступает от Диспетчера ввода/вывода
// Возвращаемое значение:
// NTSTATUS - в случае нормального //завершения STATUS_SUCCESS
// или код ошибки STATUS_Xxx
NTSTATUS Write (IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
PDEVICE_EXTENSION pDevExt =
(PDEVICE_EXTENSION) pDevObj->DeviceExtension;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// Размер буфера для данных, отправленных //пользователем
ULONG InputLength = pIrpStack->Parameters.Write.Length;