[ главная ]   [ рейтинг статей ]   [ справочник радиолюбителя ]   [ новости мира ИТ ]



Ответов: 0
25-02-12 07:01







   Web - программирование
PHP


ASP






XML



CSS

SSI





   Программирование под ОС











   Web - технологии








   Базы Данных









   Графика






Данные




Программирование под ОС / Assembler /

Компиляция и установка драйвера

Проект, сгенерированный DriverWizard, находится в каталоге XDSP. В этом каталоге расположены файлы рабочего пространства (Workspace) VC++: XDSP.dsw, XDSP.ncd и XDSP.opt и два каталога - sys и exe. Здесь же находится файл XDSPioctl.h. В нем описаны управляющие коды, используемые при обращении к драйверу с помощью функции DeviceIOControl.

В каталоге sys находится сгенерированный DriverWizard скелет драйвера и все необходимые для компиляции файлы. В нашем случае, имеем файлы:

Function.h
заголовочный файл, предназначенный для определения функций, входящих в драйвер;
Makefile, Sources
файлы с информацией, необходиой для компиляции проекта в VC++.
XDSP.h , XDSP.cpp
файлы, содержащие класс драйвера.
XDSP.plg, XDSP.dsp
проект VC++;
XDSP.inf
скрипт для инсталляции драйвера;
XDSP.rc
файл ресурсов проекта. В основном, содержит информацию о разработчике драйвера.
XDSPDevice.cpp, XDSPDevice.h
файлы, содержащие класс устройства.

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

Makefile,Sources
файлы с информацией, необходиой для компиляции проекта в VC++.
Test_XDSP.plg, Test_XDSP.dsp
проект VC++;
Test_XDSP.cpp
исходный текст приложения.

Теперь самое время открыть проект драйвера в среде VC++ и посмотреть, что же мы имеем. Для этого надо запустить VC++ и открыть проект, используя команду File -> Open Workspace. В появившемся диалоговом окне открытия файла выберите файл XDSP.dsw. Если все вышеописанные действия выполнены правлиьно, то проект откроется в среде VC++. Для того, чтобы проект скомпилировался правильно, следует установить переменные среды DriverStudio. Для этого нужно выбрать пункт меню DriverStudio -> Driver Build Settings: На экране появится диалоговое окно установки переменных среды:

установка значений переменных среды
Рис.25 - установка значений переменных среды.

Для компиляци драйвера важны две переменные:

1.  CPU - определяет архитектуру процессора, под которую компилируется драйвер. Не стоит забывать, что Win2000 может работать на платформах i386 (классические процессоры Intel), IA64 (64-разрядные процессоры Intel) и Alpha. В нашем случае надо установить значение i386.

2.  BASEDIR - путь к пакету DDK, установленному в системе. Для того, чтобы изменить значение одной из этих переменных, надо нажать кнопку Edit: диалогового окна. Появится окно установки значений переменной.

Установив требуемое значение, нажмите кнопку Set. Чтобы закрыть окно - Exit. Задав переменные среды, нажмите кнопку Accept. Теперь можно компилировать проекты.

Драйвер может быть скомпилирован в двух конфигурациях: Checked и Free.

Checked - отладочный вариант драйвера. Такой драйвер несет в себе информацию для отладки. Естественно, что для отладки драйверов непригодны обыкновенные отладчики, входящие в комплект сред VC++, Delphi и т.п. Все они работают в 3-м кольце привилегий процессора и даже не догадываются, какие драйвера есть в системе. Для отладки драйверов применяются специальные отладчики, работающие в режиме ядра ОС. В качестве отладчика лучше всего использовать SoftIce, поставляемый с DriverStudio.

Free - драйвер не несет отладочную информацию.
Активную конфигурацию можно выставить при помощи пункта меню Build -> Set Active Configuration:

Особенность сгенерированного DriverWizard рабочего пространства состоит в том, что оно содержит два проекта: XDSP и Test_XDSP. Как нетрудно догадаться, XDSP - это проект драйвера, а Test_XDSP - приложения тестирования. Информация о проектах выводится в окне Workspace среды VC++.

В каждый отдельный момент времени можно компилировать только активный проект. Имя активного проекта выводится жирным шрифтом. Сделать активным другой проект просто: надо щелкнуть на его названии правой клавишей мыши и в выпавшем контекстном меню выбрать пункт Set as Active Project (Сделать активным проектом).

Теперь можно выполнять компиляцию проекта. Если в процессе компиляции появляются сообщения об ошибках - значит вы не совсем точно следовали инструкциям, изложенным выше: или не скомпилировали библиотеки DriverWorks, или не установили переменные среды.

После компиляции драйвера следует скомпилировать тестовое приложение Test_XDSP. Оно должно скомпилироваться без каких-либо проблем.

Если все операции прошли гладко - то можете себя поздравить: драйвер готов к работе. Хотя, естественно, он не выполняет никаких разумных действий. Теперь можно протестировать наш драйвер.

После компиляции мы получили файл драйвера XDSP.sys. Он находится в каталоге .../XDSP/sys/obj/i386. В этом каталоге будут находится скомпилированные DriverStudio драйвера. Но для инсталляции кроме самого драйвера еще нужен скрипт XDSP.inf. Он обычно находится в самом каталоге XDSP.

Итак, для установки драйвера в системе предполагается наличие в системе PCI - карточки XDSP-680. После установки карточки (или перепрограммирования ее из среды Foundation) следует перезагрузить компьютер. При загрузке компьютер обнаружит новое устройство и потребует предоставить драйвер для него. Если же не потребует - значит в системе есть более ранняя версия драйвера. Для этого надо открыть список устройств, установленных на компьютере и обновить драйвер для устройства. Для этого надо указать путь к скрипту xdsp.inf и к файлу драйвера xdsp.sys.

Если же Вы разрабатываете драйвер, который не управляет каким-либо устройством или это устройство не является PnP - необходимо просто установить драйвер стандартными средствами Windows: Пуск -> Настройка -> Панель управления -> Установка оборудования. Когда Windows выведет полный список типов устройств и спросит, какое устройство Вы хотите установить, выберите свой тип устройства.

Если разработанный Вами драйвер не подходит под какой-либо из известных классов устройств, то "Другие устройства" также являются неплохим вариантом. Такая ситуация тоже случается нередко - мне, например, приходилось разрабатывать драйвер для программатора микроконтроллеров, подключавшегося через параллельный порт. Конечно же, он не подходил под какой-либо из известных в Windows типов устройств.

После того, как драйвер будет установлен, нужно будет проверить его функционирование. Запустите скомпилированный файл test_xdsp.exe с параметрами test_xdsp r 32 (команда прочитать 32 байта из устройства). Должно появиться сообщение, похожее на это:

C:XDSPexeobjchki386>Test_XDSP.exe r 32
Test application Test_XDSP starting...
Device found, handle open.
Reading from device - 0 bytes read from device (32 requested).
-, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -,
-, -, -, -, -, -,
В данном случае приложение установило связь с драйвером и прочитало из него 32 байта. Функция чтения в драйвере не определена, поэтому, естественно, драйвер вернет абракадабру. Если же будет получено сообщение вида
C:...ProjectsXDSPdrvexeobjchki386>Test_XDSP.exe r 32
Test application Test_XDSP starting...
ERROR opening device: (2) returned from CreateFile
Exiting...
- то приложение не смогло установить связь с драйвером. Следует попробовать переустановить драйвер.

 

2.3 Наращивание функциональных возможностей драйвера.

Рассмотрим подробно текст драйвера, сгенерированного DriverWizard и внесем в него необходимые изменения.

В проекте пристствуют всего два класса:

XDSP
класс драйвера;
XDSPDevice
класс устройства.
Также есть несколько глобальных функций и переменных:

PNPMinorFunctionName - возвращает строку с текстовым названием кода функции IOCTL. Эта функция используется при отладке, когда надо перевести числовое значение кода IOCTL в строку с его названием.

POOLTAG DefaultPoolTag('PSDX') - используется совместно с BoundsChecker для отслеживания возможных переполнений буфера и утечек памяти.

KTrace t("XDSPdrv") - глобальный объект трассировки драйвера. Этот объект используется для вывода сообщений трассировки при работе драйвера. Использование объекта трассировки аналогично использованию класса iostream в С++. Вывод отладочных сообщений производится при помощи оператора <<. Примеры использования объекта трассировки неоднократно встречаются в тексте драйвера, например:

t << "m_bBreakOnEntry loaded from registry,
resulting value: [" << m_bBreakOnEntry << "]\n";

В данном примере объект трассировки используется для вывода строки "m_bBreakOnEntry loaded from registry, resulting value: [" и значения логической переменной m_bBreakOnEntry. Все сообщения трассировки можно прочитать в отладчике SoftIce.

Начнем анализ текста драйвера с класса XDSP (класс драйвера). В строке 31 при помощи макроса DECLARE_DRIVER_CLASS декларируется класс драйвера XDSP. Далее следует метод DriverEntry, который вызывается при инициализации драйвера:

NTSTATUS XDSPdrv::DriverEntry(PUNICODE_STRING RegistryPath)
//В строке RegistryPath содержится ключ реестра, в котором
// система хранит информацию о драйвере.
{
//Далее выводится трассировочное сообщение, информирующее
// о вызове метода DriverEntry:
t << "In DriverEntry\n";
//После этого драйвер создает объект Params класса
// KRegistryKey и считывает данные из
//реестра для этого драйвера:
KRegistryKey Params(RegistryPath, L"Parameters");
//Далее производится проверка на успех:
if ( NT_SUCCESS(Params.LastError()) )
{
//Текст, заключенный в марос препроцессора DBG будет откомпилирован
// только в отладочной версии
//драйвера.
#if DBG
ULONG bBreakOnEntry = FALSE;
// Читается значение переменной BreakOnEntry реестра:
Params.QueryValue(L"BreakOnEntry", &bBreakOnEntry);
// Если она принимает значение true,то инициировать
// точку останова в отладчике.
if (bBreakOnEntry) DbgBreakPoint();
#endif
//Загрузить остальные параметры реестра.
LoadRegistryParameters(Params);
}
m_Unit = 0;
//Вернуть успех
return STATUS_SUCCESS;
}

Метод LoadRegistryParameters зaгружает из реестра все остальные параметры, необходимые для драйвера. Впрочем, в нашем драйвере таковых нет, и поэтому функция не выполняет никаких полезных действий (просто загружает значение переменной m_bBreakOnEntry).

void XDSPdrv::LoadRegistryParameters(KRegistryKey &Params)
{
m_bBreakOnEntry = FALSE;
Params.QueryValue(L"BreakOnEntry", &m_bBreakOnEntry);
t << "m_bBreakOnEntry loaded from registry, resulting value:
[" << m_bBreakOnEntry << "]\n";
}

На этом заканчивается секция инициализации драйвера. Далее следует метод AddDevice. Он вызывается, когда система обнаруживает устройство, за которое отвечает драйвер (обычно это происходит при загрузке драйвера). В метод ситема передает указатель на физический объект устройства (Physical Device Object, PDO). Этот объект представляет собой некий блок информации о физическом устройстве, который используется ОС. Данный метод создает объект устройства XDSPDevice. С точки зрения системы, создается функциональный объект устройства (Functional Device Object, FDO).

NTSTATUS XDSPdrv::AddDevice(PDEVICE_OBJECT Pdo)
{
t << "AddDevice called\n";
//Здесь вызывается конструктор класса XDSPDevice.
XDSPdrvDevice * pDevice = new (
static_cast(KUnitizedName(L"XDSPdrvDevice", m_Unit)),
FILE_DEVICE_UNKNOWN,
static_cast(KUnitizedName(L"XDSPdrvDevice", m_Unit)),
0,
DO_DIRECT_IO
)
XDSPDevice(Pdo, m_Unit);
//m_Unit - количество таких устройств в системе.
if (pDevice == NULL)
//Не удалось создать объект устройства. Похоже, произошла какая-то ошибка.
{
t << "Error creating device XDSPdrvDevice"
<< (ULONG) m_Unit << EOL;
return STATUS_INSUFFICIENT_RESOURCES;
}
//Получить статус создания устройства.
NTSTATUS status = pDevice->ConstructorStatus();
if ( !NT_SUCCESS(status) )
//Похоже, устройство создано, но неудачно; произошла ошибка.
{
t << "Error constructing device XDSPdrvDevice"
<< (ULONG) m_Unit << " status " << (ULONG) status << EOL;
delete pDevice;
}
else
{
m_Unit++; //Устройство создано удачно
}
//Вернуть статус устройства.
return status;
}

Все. Работа объекта драйвера на этом окончена. Как мы можем видеть, объект драйвера практически не выполняет каких-либо функций управления аппаратурой, но он жизненно необходим для правильной инициализации драйвера. В нашем случае НЕ ТРЕБУЕТСЯ вносить какие-либо изменения в текст, сформированный DriverWizard.

Основным классом драйвера является класс устройства. Класс устройства XDSPdrvDevice является подклассом класса KpnpDevice. Конструктор получает два параметра: указатель на PDO и номер драйвера в системе.

XDSPdrvDevice::XDSPdrvDevice(PDEVICE_OBJECT Pdo, ULONG Unit) :
KPnpDevice(Pdo, NULL)
{
t << "Entering XDSPdrvDevice::XDSPdrvDevice (constructor)\n";
//Здесь проверяется код ошибки, которую вернул конструктор суперкласса. В случае
//успешного создания объекта базового класса значение переменной m_ConstructorStatus
//будет NT_SUCCESS.
if ( ! NT_SUCCESS(m_ConstructorStatus) )
{
//Ошибка в создании объекта устройства
return;
}
//Запомнить номер драйвера
m_Unit = Unit;
//Инициализация устройства нижнего уровня. В роли устройства нижнего уровня в нашем
//драйвере выступает PDO. Но в случае стека драйверов в качестве устройства нижнего
//уровня может выступать объект устройства другого драйвера.
m_Lower.Initialize(this, Pdo);
// Установить объект нижнего уровня для нашего драйвера.
SetLowerDevice(&m_Lower);
// Установить стандартную политику PnP для данного устройства.
SetPnpPolicy();
}
Порядок вызова методов m_Lower.Initialize(this, Pdo), SetLowerDevice(&m_Lower) и SetPnpPolicy() является жизненно важным. Его нарушение может вызвать серьезные сбои в работе драйвера. Не стоит редактировать текст конструктора, сгенерированный DriverWizard.

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

XDSPdrvDevice::~XDSPdrvDevice()
{
t << "Entering XDSPdrvDevice::~XDSPdrvDevice() (destructor)\n";
}
Метод DefaultPnp - виртуальная функция, которая должна быть переопределена любым объектом устройства. Эта обработчик по умолчанию для IRP-пакета, у которого старший код функции (major function code) равен IRP_MJ_PNP. Драйвер обрабатывает некоторые из таких пакетов, у которых младший код функции равен IRP_MN_STOP_DEVICE, IRP_MN_START_DEVICE и т.п. (см. ниже) также при помощи виртуальных функций. Но те пакеты, которые не обрабатываются объектом устройства, передаются этой функции. Она ничего с ними не делает, а просто передает их устройству нижнего уровня (если такое есть, конечно). Не стоит изменять текст этой функции.
NTSTATUS XDSPdrvDevice::DefaultPnp(KIrp I) 
{
t << "Entering XDSPdrvDevice::DefaultPnp with IRP minor function="
<< PNPMinorFunctionName(I.MinorFunction()) << EOL;

I.ForceReuseOfCurrentStackLocationInCalldown();
return m_Lower.PnpCall(this, I);
}
Метод SystemControl выполняет похожую функцию для IRP-пакетов, у которых старший код функции IRP_MJ_SYSTEM_CONTROL. Он также является виртуальной функцией и не выполняет никаких полезных действий, а просто передает IRP-пакет устройству нижнего уровня. Что-то менять в тексте этого метода надо только в том случае, если наше устройство является WMI-провайдером.
NTSTATUS XDSPdrvDevice::SystemControl(KIrp I) 
{
t << "Entering XDSPdrvDevice::SystemControl\n";

I.ForceReuseOfCurrentStackLocationInCalldown();
return m_Lower.PnpCall(this, I);
}
Метод Invalidate вызывается, когда устройство тем или иным образом завершает свою работу: из функций OnStopDevice, OnRemoveDevice а также при всевозможных ошибках. Метод Invalidate объекта устройства также вызывается из деструктора. Его можно вызывать несколько раз - не произойдет ничего страшного; но в методах Invalidate нет никакой защиты от реентерабельности. Т.е. если при работе метода Invalidate возникает какая- либо ошибка и из-за этого Invalidate должен будет вызваться снова, то ни DriverWorks, ни ОС Windows не станут этому мешать. Разработчик должен сам предусмотреть такую возможность и принять меры, чтобы подобная ситуация не привела к нехорошим последствиям.

В методе Invalidate объекта устройства вызываются методы Invalidate всех ресурсов, которые использует драйвер: областей памяти, регистров, контроллеров DMA и т.п. При этом выполняется процедура, обратная процедуре инициализации: освобождаются все ресурсы, используемые объектом, закрываются все его хэндлы, но сам объект не уничтожается и может быть проинициализирован снова. В нашем простом случае нет смысла что-либо корректировать в тексте этого метода - DriverWizard все сделал за нас. Еще бы, ведь наше устройство имеет только один ресурс - диапазон адресов памяти. Но при проектировании более сложных драйверов следует обращать внимание на данный метод. Если разработчик добавляет какие-либо системные ресурсы вручную, то он должен включить соответствующий код в метод Invalidate.

VOID XDSPdrvDevice::Invalidate()
{
//Вызвать метод Invalidate для диапазона адресов памяти.
m_MainMem.Invalidate();
}
Далее следует виртуальная функция OnStartDevice. Она вызывается при приходе IRP- пакета со старшим кодом IRP_MJ_PNP и кодом подфункции IRP_MN_START_DEVICE. Обычно это происходит при старте драйвера после выполнения всех необходимых проверок и инициализаций. В этой функции драйвер инициализирует физическое устройство и приводит его в рабочее состояние. Также здесь драйвер получает список ресурсов, которые имеются в устройстве. На основе этого списка ресурсов выполняется их инициалиция. Хотя мы не вносим изменений в данную функцию, но нельзя не отметить ее огромную важность. Именно в данной функции выполняется инициализация устройства и получение списка его ресурсов. По-другому мы их получить никак не можем, т.к. имеем дело с PnP устройством, для которого система распределяет ресурсы самостоятельно.
NTSTATUS XDSPdrvDevice::OnStartDevice(KIrp I)
{
t << "Entering XDSPdrvDevice::OnStartDevice\n";
NTSTATUS status = STATUS_SUCCESS;
I.Information() = 0;

//Здесь драйвер получает список ресурсов устройства
PCM_RESOURCE_LIST pResListRaw = I.AllocatedResources();
PCM_RESOURCE_LIST pResListTranslated = I.TranslatedResources();

// Наше устройство является PCI - карточкой и в своем конфигурационном поле содержит
//базовые адреса диапазонов памяти и портов ввода-вывода. Получаем эти данные
KPciConfiguration PciConfig(m_Lower.TopOfStack());

// Инициализируем каждый диапазон отображения адресов. Теперь, после инициализации,
//базовый адрес отображенного диапазона в виртуальном адресном пространстве
//процессора может быть получен при помощи вызова метода Base(). Физический адрес на
//шине адреса ЦП - при помощи CpuPhysicalAddress().

status = m_MainMem.Initialize(
pResListTranslated,
pResListRaw,
PciConfig.BaseAddressIndexToOrdinal(0)
);

if (!NT_SUCCESS(status))
{
//Неудача при инициализации области памяти
Invalidate();
return status;
}
//Сюда можно добавить код, выполняющий необходимую инициализацию, специфичную для
//этого устройства
return status;
}
Виртуальная функция OnStopDevice вызывается при остановке устройства. В этом случае система посылает драйверу IRP с старшим кодом IRP_MJ_PNP и кодом подфункции IRP_MN_STOP_DEVICE. Драйвер должен осободить все используемые ресурсы.
NTSTATUS XDSPdrvDevice::OnStopDevice(KIrp I)
{
NTSTATUS status = STATUS_SUCCESS;
t << "Entering XDSPdrvDevice::OnStopDevice\n";
//Освободить ресурсы устройства
Invalidate();

// Здесь добавляется код, специфичный для данного устройства.

return status;
}
Виртуальная функция OnRemoveDevice вызывается при извлечении устройства из системы. При этом системная политика PnP сама позаботится об удалении PDO.
NTSTATUS XDSPdrvDevice::OnRemoveDevice(KIrp I)
{
t << "Entering XDSPdrvDevice::OnRemoveDevice\n";
// Освободить ресурсы устройства
Invalidate();

// Здесь добавляется код, специфичный для данного устройства.

return STATUS_SUCCESS;
}
Иногда бывает необходимо отменить обработку IRP, уже поставленного в очередь (такие запросы иногда посылает диспетчер В/В). Когда такая ситуация может возникнуть?

Представим такую ситуацию: в приложении пользователя поток послал нашему драйверу запрос на ввод-вывод и завершил свою работу. А IRP-пакет попал в очередь запросов и терпеливо ждет своей очереди на обработку. Конечно же, обработка такого "бесхозного" IRP-пакета должна быть отменена. Для этого драйвером поддерживается целый механизм отмены обработки запросов. В Win2000 DDK подробно описано, почему ЛЮБОЙ драйвер должен поддерживать этот механизм. Это связано, в основном, с проблемами надежности и устойчивости работы системы. Ведь сбой в драйвере - это, практически, сбой в ядре ОС.

В классе KPnPDevice механизм отмены запроса реализован при помощи метода CancelQueuedIrp.

VOID XDSPdrvDevice::CancelQueuedIrp(KIrp I)
{
//Получаем очередь IRP-пакетов этого устройства.
KDeviceQueue dq(DeviceQueue());

//Проверить, является ли IRP, который должен быть отменен, тем пакетом, который должен
//быть обработан.
if ( (PIRP)I == CurrentIrp() )
{
//Уничтожить пакет.
CurrentIrp() = NULL;
//При вызове метода CancelQueuedIrp устанавливается глобальная системная
//защелка (SpinLock). Теперь следует ее сбросить.
CancelSpinLock::Release(I.CancelIrql());
t << "IRP canceled " << I << EOL;
I.Information() = 0;
I.Status() = STATUS_CANCELLED;
//Обработать следующий пакет.
PnpNextIrp(I);
}
//Удалить из очереди пакет. Если это удалось, то функция вернет true.
else if (dq.RemoveSpecificEntry(I))
{
// Это удалось. Теперь сбрасываем защелку.
CancelSpinLock::Release(I.CancelIrql());
t << "IRP canceled " << I << EOL;
I.Information() = 0;
I.PnpComplete(this, STATUS_CANCELLED);
}
else
{
//Неудача. Сбрасываем защелку.
CancelSpinLock::Release(I.CancelIrql());
}
}
Метод StartIo является виртуальной функцией. Она вызывается системой, когда драйвер готов обрабатывать следующий запрос в очереди. Это чрезвычайно важная функция: она является диспетчером всех запросов на ввод-вывод, поступаемых к нашему драйверу. В функции вызываются обработчики запросов на чтение, запись а также обработчики вызовов IOCTL. К счастью, умный DriverWizard генерирует скелет функции автоматически и вносить изменения в нее в нашем простом случае не требуется. В принципе, в эту функцию можно ввести какие-то дополнительные проверки IRP-пакетов.
VOID XDSPdrvDevice::StartIo(KIrp I)
{
t << "Entering StartIo, " << I << EOL;

//Здесь надо проверить, отменен этот запрос или нет. Это производится
// при помощи вызова метода TestAndSetCancelRoutine. Также этот метод
// устанавливает новую функцию отмены пакетов, если это необходимо.
// Адрес новой функции передается вторым параметром. Если он равен
// NULL,то вызывается старая функция. Если пакет должен быть отменен,
// функция вернет FALSE.
if ( !I.TestAndSetCancelRoutine(
LinkTo(CancelQueuedIrp),
NULL,
CurrentIrp()) )
{

//Пакет отменен.
return;
}

// Начать обработку запроса.
// Выбрать необходимую функцию
switch (I.MajorFunction())
{
case IRP_MJ_READ:
//Чтение
SerialRead(I);
break;
case IRP_MJ_WRITE:
//Запись
SerialWrite(I);
break;
case IRP_MJ_DEVICE_CONTROL:
//IOCTL
switch (I.IoctlCode())
{
default:
//Мы обрабатываем пакет, который не должен быть обработан.
//Поэтому просто выходим.
ASSERT(FALSE);
break;
}
break;
default:
// Драйвер занес в очередь какой-то непонятный пакет,
//он не должен быть обработан.
ASSERT(FALSE);
PnpNextIrp(I);
break;
}
}

Метод Create вызывается, когда пользовательское приложение пытается установить связь с драйвером при помощи вызова API CreateFile(). Обычно этот запрос обрабатывается в нашем объекте устройства и нет смысла пересылать запрос устройству нижнего уровня.

NTSTATUS XDSPdrvDevice::Create(KIrp I)
{
NTSTATUS status;

t << "Entering XDSPdrvDevice::Create, " << I << EOL;

//Здесь можно вставить код пользователя, который должен быть вызван при установлении
//приложением связи с устройством.

status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
t << "XDSPdrvDevice::Create Status " << (ULONG)status << EOL;
return status;
}

Аналогично метод Close вызывается при разрыве связи приложения с драйвером.

NTSTATUS XDSPdrvDevice::Close(KIrp I)
{
NTSTATUS status;
t << "Entering XDSPdrvDevice::Close, " << I << EOL;

//Здесь можно вставить код пользователя, который должен быть вызван при разрыве
//приложением связи с устройством.

status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
t << "XDSPdrvDevice::Close Status " << (ULONG)status << EOL;
return status;
}
В этих методах можно ввести проверки каких-либо условий. Отвлечемся на секунду от нашей PCI-карточки и обратим внимание на другой хороший пример - тот же программатор микроконтроллеров. Предположим, пользователь подключил программатор к компьютеру и начинает записывать в память микроконтроллера разработанную им программу. В принципе, ничто не помешает ему открыть еще одну копию программы и писать в ту же микросхему что-то совсем другое. В результате, в эту несчастную микросхему запишется невообразимая каша. Для того, чтобы избежать такой ситуации, в объекте драйвера надо установить флаг, который будет показывать, свободно ли это устройство, или оно уже кем-то используется. Это может выглядеть так:
NTSTATUS MyPrettyDevice::OnStartDevice(KIrp I)
{
NTSTATUS status = STATUS_SUCCESS;
I.Information() = 0;

. . . //Какая-то инициализация - может, PCI,
//может - какое-то другое оборудование...

//Устройство только что заработало - конечно, оно свободно...
m_AlreadyUsed = false;
return status;
}

NTSTATUS MyPrettyDevice::Create(KIrp I)
{
NTSTATUS status;
if (m_AlreadyUsed)
//Это устройство уже используется кем-то. Нельзя допустить его использование
//несколькими приложениями одновременно.
//Возвращаем ошибку.
status = I.PnpComplete(this, STATUS_INVALID_PARAMETER, IO_NO_INCREMENT);
else
{
//Это устройство свободно. Устанавливаем флаг и возвращаем успех.
m_AlreadyUsed = false;
status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
}
return status;
}

NTSTATUS MyPrettyDevice::Close(KIrp I)
{
NTSTATUS status;

//Пользователь закончил работу с устройством, теперь оно свободно.
//Сбрасываем флаг.
m_AlreadyUsed = false;
status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
return status;
}
Функция SerialRead вызывается, когда драйвер получает запрос на чтение. Это важная функция. Т.к. мы хотим, чтобы приложение пользователя могло читать и писать в память микросхемы, то именно сюда необходимо добавлять наш код. Все фрагменты кода, добавленные программистом, будут выделены жирным шрифтом:
//This code was added by the programmer
Фактически в данном методе мы должны прочитать содержимое памяти и передать его приложению пользователя. Но тут самое время вспомнить, что плата обменивается с памятью 4- байтными словами. Поэтому для операций с памятью следует применять метод ind/outd.
void XDSPdrvDevice::SerialRead(KIrp I)
{
t << "Entering XDSPdrvDevice::SerialRead, " << I << EOL;
NTSTATUS status = STATUS_SUCCESS;
//Здесь мы получаем буфер пользователя. Он передается через Irp.
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
//Теперь pBuffer - указательна буфер пользователя.
//Здесь мы получаем число 4-байтных слов, которое должно быть прочитано. Оно также
//передается через Irp, как запрашиваемое количество байт для чтения.
ULONG dwTotalSize = I.ReadSize(CURRENT);
ULONG dwBytesRead = dwTotalSize;

//Здесь мы читаем заданное число байт из памяти устройства. Плата XDSP680 обменивается
//с памятью 4-байтными словами.Начальный адрес - 0, dwTotalSize 4-байтных слов будут
//прочитаны в буфер pBuffer.
m_MainMem.ind(0,(ULONG*)pBuffer,dwTotalSize);
//Возвращаем количество прочитанных слов

I.Information() = dwBytesRead;

I.Status() = status;

//Обработать следующий IRP-пакет.
PnpNextIrp(I);
}
Метод SerialWrite работает практически так же, только он записывает данные в память устройства, а не считывает их.
void XDSPdrvDevice::SerialWrite(KIrp I)
{
t << "Entering XDSPdrvDevice::SerialWrite, " << I << EOL;
NTSTATUS status = STATUS_SUCCESS;
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();

ULONG dwTotalSize = I.WriteSize(CURRENT);
ULONG dwBytesSent = dwTotalSize;
m_MainMem.outd(0,(ULONG*)pBuffer,dwTotalSize);
I.Information() = dwBytesSent;


I.Status() = status;
PnpNextIrp(I);
}
Как мы упоминали ранее, для большинства драйверов устройств недостаточно функций чтения и записи. Мало-мальски сложное устройство требует еще и множества других операций: получить состояние, получить информацию об устройстве, как-то отконфигурировать его. Для выполнения этих задач служат функции управления вводом-выводом, IO Control; сокращенно - IOCTL. IOCTL предоставляет программисту возможность разработать практически неограниченное количество различных функций управления устройством.

И драйвер, и приложение пользователя различают, какую функцию управления устройством вызвать, при помощи IOCTL-кодов. Такой код представляет собой обыкновенное 32-разрядное число. Для удобства ему директивой #define задают какое-то понятное имя. Например, в нашем случае зададим IOCTL-код, при получении которого драйвер будет возвращать количество памяти "на борту" PCI-устройства.

#define    XDSPDRV_IOCTL_GETMEMSIZE    0x800
Если при чтении драйверу посылается IRP-пакет со старшим кодом функции IRP_MJ_READ, при записи - IRP_MJ_WRITE, то при вызове функции DeviceIOControl для нашего устройства драйвер получает пакет со старшим кодом IRP_MJ_IOCONTROL и младшим - код самой IOCTL-функции. Метод DeviceControl вызывается при получении драйвером IRP со старшим кодом IRP_MJ_DEVICE_CONTROL. Она действует подобно методу StartIo.В зависимости от кода IOCTL производится вызов соответствующей функции.
NTSTATUS XDSPdrvDevice::DeviceControl(KIrp I) 
{
NTSTATUS status;

t << "Entering XDSPdrvDevice::Device Control, " << I << EOL;
switch (I.IoctlCode())
{
case XDSPDRV_IOCTL_GETMEMSIZE:
//Получен поределенный нами IOCTL-код XDSPDRV_IOCTL_GETMEMSIZE.
//Вызвать соответствующий обработчик.
status = XDSPDRV_IOCTL_GETMEMSIZE_Handler(I);
break;

default:
//Этот код не определен.
status = STATUS_INVALID_PARAMETER;
break;
}

if (status == STATUS_PENDING)
// Если драйвер по каким-то причинам отложил обработку запроса, переменной status
//присваивается значение STATUS_PENDING. Этот код будет возвращен методом
//DeviceControl.
{
return status;
}
else
//В противном случае завершаем обработку пакета.
{
return I.PnpComplete(this, status);
}
}
Метод XDSPDRV_IOCTL_GETMEMSIZE_Handler является обработчиком IOCTL - кода XDSPDRV_IOCTL_GETMEMSIZE. Получив этот код, драйвер возвращает общий объем памяти устройтсва. Шаблон метода сгенерирован DriverWizard, но программист должен написать практически весь его код.
NTSTATUS XDSPdrvDevice::XDSPDRV_IOCTL_GETMEMSIZE_Handler(KIrp I)
{
NTSTATUS status = STATUS_SUCCESS;
t << "Entering XDSPdrvDevice::XDSPDRV_IOCTL_GETMEMSIZE_Handler, " << I << EOL;
//Количество памяти будет возвращено как число unsigned long. Поэтому определяем
//указатель на unsigned long.
unsigned long *buf;
//Получаем указатель на буфер пользователя
buf=(unsigned long*) I.UserBuffer();
//Записываем туда количество памяти нашего устройства. Получаем его при помощи
//метода Count объекта m_MainMem.
*buf=m_MainMem.Count();
//Длина возвращаемых нами данных равна длине числа unsigned long.
I.Information() = sizeof(unsigned long);
//Возвращаем STATUS_SUCCESS
return status;
}
Написание драйвера завершено. Возможно, у Вас сложилось впечатление, что DriverWizard может практически все и написание драйвера - очень простая задача. Но не следует забывать, что наш драйвер - всего-то простейшая демонстрационная программа, которая практически не выполняет никаких полезных действий. Написание реальных драйверов является гораздо более сложной задачей.

Если бы драйвер был написан с использованием пакета DDK, то он бы имел практически ту же структуру и почти тот же код (правда, не объектно-ориентированный). Но в таком случае весь драйвер пришлось бы писать вручную, а DriverWizard генерирует скелет драйвера автоматически. Это сильно облегчает процесс разработки драйвера, позволяя программисту не заботиться о написании скелета драйвера и предохраняя его от возможных ошибок.




Комментарии

 Ваш комментарий к данному материалу будет интересен нам и нашим читателям!



Последние статьи: Программирование под ОС / Assembler /

Первая программа на linux
23-05-2010   

Ассемблер, который я буду использовать - NASM (Netwide Assembler, nasm.2y.net). Этот выбор объясняется тем, что: Во первых, он мультиплатформенный, т.е. для портирования программы на разные ОС достаточно только изменить код взаимодействия с системой, а всю программу переписывать не нужно... подробнее

Кол. просмотров: общее - 3594 сегодня - 1

Использование пакета NuMega Driver Studio для написания WDM - драйверов устройств
17-05-2010   

Разработка WDM - драйвера с использованием только DDK является сложной и трудоемкой задачей. При этом приходится выполнять много однотипных операций: создание скелета драйвера, написание inf - файла для его установки, создание приложения для тестирования и т.п... подробнее

Кол. просмотров: общее - 3213 сегодня - 2

Система классов DriverWorks
17-05-2010   

Возможно, идея писать драйвера объектно-ориентированными и кажется на первый взгляд нелогичной. Но при более близком знакомстве с DriverStudio и с драйверами в общем, оказывается, что это не так уж страшно и довольно удобно... подробнее

Кол. просмотров: общее - 3242 сегодня - 1

Объект устройства device object
17-05-2010   

Объекты устройств являются экземплярами класса KDevice или KPnpDevice. Эти классы являются краеугольными камнями архитектуры DriverWorks: они представляют собой как бы программный образ тех устройств, которые присутствуют в системе... подробнее

Кол. просмотров: общее - 3278 сегодня - 0

Объекты для управления оборудованием
17-05-2010   

Как было упомянуто выше, объект устройства управляет работой устройства при помощи специальных объектов, управляющих работой оборудования - портами В/В, прерываниями, памятью, контроллерами ПДП. Драйвер создает эти объекты для представления физических параметров устройства... подробнее

Кол. просмотров: общее - 3462 сегодня - 1



  WWW.COMPROG.RU - 2009-2012 | Designed and Powered by Zaipov Renat | Projects