Visual C + +: Робота з USB пристроями, Різне, Програмування, статті

Стаття присвячена основам створення додатків в середовищі windows, що взаємодіють з usb пристроями. Коротко розглядається архітектура usb шини, програмна модель і уривки коду реалізації драйвера usb пристрою, доступ до драйвера з додатку.


Короткий огляд шини


usb – universal serial bus – універсальна послідовна шина. Оскільки шина послідовна, то в кожен момент часу передається тільки один потік даних. Це слід тримати в голові, оскільки коли ми будемо розглядати програмну модель, може виникнути відчуття, що дані передаються паралельно. Шина usb чимось схожа на ethernet – дані передаються пакетами. Кожен пакет має заголовок відповідає за транспортування і маршрутизацію даних. Але є й істотно відмінність: в usb смуга (bandwidth) (цей термін просто кажучи можна розуміти як “загальна пропускна здатність”) шини ділиться між пристроями не за принципом “хто перший зайняв” (random access). У usb ресурси розподіляються централізовано – концентратором (hub) шини. У шини може бути тільки один кореневий концентратор (root hub), керуючий роботою всієї шини (він то як раз і розподіляє ресурси шини між пристроями). Тому не можна з’єднати два комп’ютер безпосередньо проводом (на зразок нуль-модему) через usb – вийде конфігурація з двома кореневими концентраторами. До кореневого концентратора підключаються пристрої – всього до 127. Пристрій може бути в свою чергу концентратором, контролюючим нижележащий сегмент шини. Таким чином, usb шина може виглядати як многоранговая зірка (дерево).


Специфікація usb 2.0 передбачає три можливі швидкості передачі:


high speed – 480Мб / c

full speed – 12Мб / c

low speed – 1.5Мб / c

Слід пам’ятати, що максимальну швидкість повинен підтримувати кореневої концентратор. До 12Мб / c концентратора можна підключити низькошвидкісне пристрій і не можна – високошвидкісне (тобто можна, але воно буде працювати на швидкості 12Мб / с, а не 480). Слід зауважити, що сучасні чипсети персональних комп’ютерів найчастіше забезпечую швидкість тільки 12Мб / c (вони хоч і позиціонуються як usb 2.0 сумісні, але по суті є usb1.1), тому не варто вважати, що usb забезпечить у всіх випадках життя високошвидкісний обмін.


Розрізняють 4 різних типи передачі по usb:


Керуючий – використовується для конфігурації пристрою на шині, також може використовуватися для специфічних для пристрою цілей;

Пакетний – передача великих пакетів інформації, яка припускає великі затримки у доставці даних;

“По перериванню” – надійна, короткочасна передача (наприклад, коду клавіші від клавіатури);

Ізохронний – для передачі даних в режимі реального часу (з мінімальною затримкою).

Кожен пристрій надає одну (або декілька) т.зв. функцію. Деякі пристрої можуть містити також один (або декілька) концентратор, до якого підключаються підлеглі пристрої.

Кожна функція може мати кілька точок підключення (endpoint). Кожна точка відповідає за певний тип передачі даних. Кожен пристрій має управляючу точку підключення (control endpoints).

2. Програмна модель


Розглядаючи програмну модель usb для windows перш за все варто відзначити такі особливості:

1) Кожна usb пристрій повинен обслуговуватися власним драйвером. На відміну від, скажімо, пристроїв, що підключаються до lpt, для яких наявність власного драйвера в загальному необов’язково. На шину usb не можна просто передати сирої потік даних.

2) Усі usb контролери відповідають специфікації acpi, тобто підтримують функції pnp і управління живленням. Тому працювати з пристроєм можна тільки якщо воно підключено до шини (як це не дивно j ).

3) Драйвера в windows утворюють т.н стек, за яким і передаються дані вгору і вниз, не слід намагатися працювати безпосередньо з портами контролера.

Тримаючи в голові ці три пункти (чого не потрібно робити j), подивимося як же працювати з цим господарством.

Для початку розглянемо апаратну реалізацію на прикладі популярного (в недавньому минулому) чіпсета i815. Обслуговуванням всіх пристроїв введення / виводу в цьому чіпсеті займається спеціалізований контролер – ich (i / o controller hub) – 82801ba. Щоб перелічити всі його функції не вистачить листа. Нас буде цікавити той факт, що до складу цієї мікросхеми входить у тому числі два незалежних usb контролера, кожен з яких має по два порти. Контролери підтримують швидкість передачі 12Мб / c (тобто usb 1.1). Кожен контролер має в діапазоні введення / виводу набір портів, через які ними можна керувати.

Тут згадуємо п.3 і відмовляємося від думки управляти usb контролерами безпосередньо. Виробник контролера постарався і написав для нас драйвер. Ті, хто мав досвід взаємодії з драйверами з режиму програми, можливо, вже журиться: “Що толку від цього драйвера! Потрібно знати ще його інтерфейс!”. Абсолютно вірно, але справа в тому, що нам його інтерфейс не знадобитися. В операційній системі є вже універсальний драйвер для кореневих концентраторів. Це відповідає концепції моделі драйверів: “Логічне пристрій – Фізичне пристрій”. В даному випадку драйвер кореневого концентратора (usbhub.sys) створює логічний пристрій дозволяють для верхніх драйверів абстрагуватися від подробиць роботи драйвера usb контролера. Нетерплячий читач знову вигукне: “І що толку!”. Для організації інтерфейсу з клієнтом (драйвером) існує третій драйвер – драйвер шини (usbd.sys). Його інтерфейс документований в ddk. Це набір керуючих кодів для диспетчера (просто процедура, обробна запити) запитів типу irp_mj_internal_device_control. За допомогою цих запитів можна отримати різноманітну інформацію про стан шини, кількості портів, їх статус та ін Всі ці коди мають символічні імена виду: ioctl_internal_usb_ХХХХ. Особливу увагу слід звернути на запит ioctl_internal_usb_submit_urb. Управління пристроєм, запис і читання даних здійснюється саме через цей запит. З цього випливає, щоб передати дані на шину, не можна викликати writefile для драйвера шини або кореневого концентратора – він може взагалі не мати диспетчера для irp_mj_write (. З іншого боку звернутися до диспетчера irp_mj_internal_device_control з користувальницького додатка можна. Тому згадуємо п.1 – кожен пристрій зобов’язане мати свій драйвер, який вирішує цю дилему – він має диспетчер для irp_mj_write (який викликається диспетчером вводу-виводу коли додаток викликає writefile). Все теж саме відноситься до читання. Для деяких класів пристроїв існую стандартні драйвера: для hid пристроїв (пристрої інтерфейсу – миші, клавіатури і.т.п.), usb audio – пристрої відтворення звуку (ЦАП розташовується прямо в локальній аудіоустановка, а по шині передається цифровий потік). Для пристроїв, що належать до цих класів, не потрібно драйверів.

Поговоримо трохи про модель введення / виведення. Для розробників програм користувача це, можливо, абсолютно незнайома тема. З апаратної точки зору висновок / висновок буває програмний – використання інструкцій in, out, mov а також (найбільш цікавий випадок!) використання строкових операцій з префіксом повтору (rep movsb) або з використанням контролера прямого доступу до пам’яті (dma). Однак, в даному випадку мова йде про інше. Апаратним вводом / виводом займається саме драйвер usb контролера. А нас цікавить, куди потрапляють прийняті дані (і куди містяться дані, призначені для передачі)? Існує два підходи: “буферизують” і “прямий” введення / виведення.

При буферизують виведення відбувається наступне:


1) користувача додаток викликає функцію writefile, передавши покажчик на буфер містить дані;

2) ОС викликає диспетчер irp_mj_write драйвера і передає туди (через структуру irp) покажчик на буфер даних

3) Драйвер копіює дані у свій внутрішній буфер. Після цього, можливо, повідомляє, що дані передані або відкладає це повідомлення до моменту актуальною передачі.

4) Актуальна передача здійснюється колись пізніше.

При буферизують введенні все аналогічно. Головне достоїнство цього методу – прийняті дані не можуть “прірву” (якщо тільки внутрішній буфер не переповнитися). Буферизують введення / виведення здійснює, наприклад, драйвер послідовного порту. Для повільних пристроїв введення / виводу це рекомендований спосіб.

Для швидких пристроїв, особливо передавальних дані великими пакетами, використання програмних буферів має два основних недоліки – великі накладні витрати на копіювання даних в (з) проміжний буфер, нераціонально використовується системна пам’ять. Тому в даному випадку використовується пряме введення / виведення – дані приймаються безпосередньо в буфер (або передаються з цього буфера), зарезервований користувальницької програмою. Продуктивність такого методу помітно вище. Однак виникає деякі складнощі з прийомом даних – у драйвера завжди повинен бути “під рукою” буфер. І це має забезпечити клієнт (користувацька програма або вищерозміщений драйвер).

3. Основи програмування драйвера usb пристрою


Коль ми встановили, що будь-usb пристрій (крім hid і usb audio) повинно мати власний драйвер, то розглянемо далі як влаштований такий драйвер. Будь драйвер, що задовольняє моделі драйверів nt або wdm, починається стандартної точки входу – driverentry. В цій процедурі повинні бути задані диспетчери – процедури, по засобом яких драйвер взаємодіє з системою. Розглянемо наступний лістинг. Тут і далі опущений висновок налагоджувальної інформації, перевірка повертаються значень на помилки, обробка виключень, коротше все те, що робить код надійним, але погіршує його читабельність, в коді реального драйвера, природно, слід це все передбачити. Більш того, опущені деякі деталі, які не є специфічними для usb драйверів. Проте без них драйвер просто не будуть працювати. Розробникам драйверів раджу звернутися до прикладів з ddk або driverstudio. Наведений же код розрахований в першу чергу на розробників додатків – щоб знали що по чому! j

ntstatus driverentry(in pdriver_object driverobject, in punicode_string registrypath )
{
driverobject->majorfunction[irp_mj_create] = usbdrv_create;
driverobject->majorfunction[irp_mj_close] = usbdrv_close;
driverobject->majorfunction[irp_mj_device_control] = usbdrv _processioctl;
driverobject->majorfunction[irp_mj_write] = usbdrv_write;
driverobject->majorfunction[irp_mj_read] = usbdrv_read;
driverobject->majorfunction[irp_mj_system_control] = usbdrv _processsyscontrolirp;
driverobject->majorfunction[irp_mj_pnp] = usbdrv_processpnpirp;
driverobject->majorfunction[irp_mj_power] = usbdrv_processpowerirp;

driverobject->driverextension->adddevice = usbdrv_adddevice;
driverobject->driverunload = usbdrv_unload;

return status_success; }

usbdrv_create – сповіщає драйвер, що на ньому був відкритий файл, тобто була викликана функція ntcreatefile (в режимі користувача програми зазвичай викликають цю функцію, експортовану ntdll.dll через виклик createfile з kernel32.dll; дайвера режиму ядра викликають zwcreatefile, експортовану ядром – ntoskrn.exe). Відзначимо наступне:

1. за допомогою цього диспетчера можна і відмовити у відкритті файлу – досить повернути щось відмінне від status_success;

2. в диспетчер через ім’я файлу може бути передана додаткова текстова інформація – вона просто “чіпляється” до імені ззаду.

usbdrv_close – сповіщає драйвер про закриття файлу. Викликається у відповідь на виклик closehandle з користувальницького додатки.

usbdrv_processioctl – передає драйверу керуючий код. Користувацька додаток для передачі такого коду використовує deviceiocontrol

usbdrv_write – передає драйверу буфер для запису в пристрій. Викликається менеджером вводу / виводу у відповідь на виклик writefile (ex).

usbdrv_read – передає драйверу буфер для читання.

Вищеперелічені диспетчери призначені для спілкування драйвера зі своїм клієнтом. Наступні три диспетчера використовуються системою:

usbdrv _processsyscontrolirp – обробник системних запитів. Як правило, драйвер передає їх просто нижележащему драйверу.

usbdrv_processpnpirp – обробник pnp запитів. usb пристрій відповідає специфікації acpi і його драйвер повинен мати цей диспетчер.

usbdrv_processpowerirp – обробник запитів системи управління живленням.

usbdrv_adddevice – драйвер будь-якого pnp пристрої зобов’язаний мати цю функцію. Вона викликається при виявленні нового пристрою, що обслуговується драйвером.

usbdrv_unload – викликається при вивантаженні драйвера з пам’яті

Далі ми не будемо розглядати повністю код, розглянемо тільки важливі моменти, які повинні бути цікаві також і прикладним програмістам. Будемо розглядати код в “подієвому” порядку.

Після того, як система виявляє пристрій, обслуговуване нашим драйвером, вона викликає функцію adddevice. Для зіставлення драйвера виявленому пристрою використовується реєстр. Ті, хто цікавиться подробицями цього процесу, може зазирнути до реєстру hklmsystemcurrentcontrolsetusb. За pid (product id) і vid (vendor id) встановлюється ім’я сервісу. Якщо сервіс ще не завантажений – він завантажується в пам’ять і викликається driverentry. Потім викликається та сама функція adddevice. Якщо драйвер не завантажений в пам’ять він для початку туди завантажується. Якщо ж не найденa відповідний запис у реєстрі – з’являється знайома напис про виявлення нового пристрою.

Розглянемо, що робить функція adddevice:


ntstatus usbdrv_pnpadddevice(in pdriver_object driverobject, in pdevice_object physicaldeviceobject )
{
ntstatus ntstatus = status_success;
pdevice_object deviceobject = null;
pdevice_extension deviceextension;
unicode_string devicelink;
ulong i;

ntstatus = ioregisterdeviceinterface(physicaldeviceobject, (lpguid)&guid_class_usb_drv, null, &devicelink);

ntstatus = iosetdeviceinterfacestate(&devicelink, true);
ntstatus = iocreatedevice (driverobject,
sizeof (device_extension),
null,
file_device_unknown,
file_autogenerated_device_name, false,
&deviceobject);
deviceextension = (deviceobject)->deviceextension;

rtlcopymemory(deviceextension ->devicelinknamebuffer, devicelink.buffer, devicelink.length);

deviceextension ->pdo = physicaldeviceobject;

deviceextension ->topstackdevice =

ioattachdevicetodevicestack(deviceobject, physicaldeviceobject);

deviceobject ->flags /= do_direct_io;

deviceobject ->flags &= ~do_device_initializing;

return status_success;
}

Насамперед, потрібно зареєструвати інтерфейс пристрою за допомогою ioregisterdeviceinterface. Якщо пристрій підтримує кілька інтерфейсів (наприклад, контролер може обслуговувати миша і клавіатуру) – Це функція може викликатися кілька разів. Далі створюється об’єкт – пристрій device_object. Якщо у вас встановлений softice, можете спробувати набрати в режимі налагодження device і знайти свій пристрій. Після того, як інтерфейс буде дозволений (iosetdeviceinterfacestate), автоматично буде створено відповідну символічна посилання в розділі dosdevices, за допомогою якої користувальницькі додатки зможуть отримати доступ до пристрою. Створений об’єкт пристрій слід підключити (ioattachdevicetodevicestack) до стека пристроїв. Після цього наш драйвер в контексті створеного пристрою буде обробляти запити pnp і системи управління живленням. Крім того, наш драйвер зможе відправляти irp запити вниз по стеку, до якого він пріаттачілся. Одним з перших буде отриманий pnp запит irp_mn_start_device, за допомогою якого драйвер отримає всю необхідну інформацію про пристрій. Наведемо код відповідного обробника:

ntstatus usbdrv_onstartdevice(in pdevice_object deviceobject )
{
ntstatus ntstatus = status_success;
pdevice_extension pdevext = null;
purb purb = null;
pusb_device_descriptor pdevdesc = null;
pusb_configuration_descriptor pconfigdesc = null;

Насамперед, отримаємо всяку корисну інформацію про пристрій, що ми збираємося обслуговувати. Відразу зауважимо, що всю роботу за нас будуть робити нижележащие драйвера (usbd.sys). Нам залишається тільки грамотно формувати запити urb – usb request block. Для цього:

Для початку отримуємо т.н deviceextension – область пам’яті, виділеної при виклику iocreatedevice, яку ми можемо використовувати на власний розсуд – т.н контекст пристрою:

pdevext = deviceobject->deviceextension;

Формуємо urb запит:

purb = exallocatepool(nonpagedpool, sizeof(struct _urb_control_descriptor_request) ) ;

pdevdesc = exallocatepool(nonpagedpool, sizeof(usb_device_descriptor) );

usbbuildgetdescriptorrequest(purb,(ushort)sizeof(struct _urb_control_descriptor_request),
usb_device_descriptor_type, 0, 0, pdevdesc, null, sizeof(usb_device_descriptor), null);

Будь-яке спілкування між драйверами відбувається за допомогою irp. У нашому випадку ми через irp передаємо наш urb запит:

pirp = iobuilddeviceiocontrolrequest(ioctl_internal_usb_submit_urb,
pdevext ->topstackdevice, null, 0, null, 0, true, &event, &iostatus);

nextstack = iogetnextirpstacklocation(pirp);
nextstack->parameters.others.argument1 = urb;

І ось кульмінація – передаємо запит нижележащему драйверу:
iocalldriver(pdevext ->topstackdevice, pirp);
При нормальному збігу обстоятельсів ми отримаємо таку інформацію про пристрій:

kdprint((“usblink device descriptor:n”));

kdprint((“————————-n”));

kdprint((“blength %dn”, pdevdesc->blength));

kdprint((“bdescriptortype 0x%xn”, pdevdesc->bdescriptortype));

kdprint((“bcdusb 0x%xn”, pdevdesc->bcdusb));

kdprint((“bdeviceclass 0x%xn”, pdevdesc->bdeviceclass));

kdprint((“bdevicesubclass 0x%xn”, pdevdesc->bdevicesubclass));

kdprint((“bdeviceprotocol 0x%xn”, pdevdesc->bdeviceprotocol));

kdprint((“bmaxpacketsize0 0x%xn”, pdevdesc->bmaxpacketsize0));

kdprint((“idvendor 0x%xn”, pdevdesc->idvendor));

kdprint((“idproduct 0x%xn”, pdevdesc->idproduct));

kdprint((“bcddevice 0x%xn”, pdevdesc->bcddevice));

kdprint((“imanufacturer 0x%xn”, pdevdesc->imanufacturer));

kdprint((“iproduct 0x%xn”, pdevdesc->iproduct));

kdprint((“iserialnumber 0x%xn”, pdevdesc->iserialnumber));

kdprint((“bnumconfigurations 0x%xn”, pdevdesc->bnumconfigurations));

Запам’ятаємо цю інформацію в контексті пристрою

pdevext ->usbdevicedescriptor = pdevdesc;

Тепер використовуємо отриману інформацію для ініціалізації пристрою:
Побудуємо ще один urb запит. В даному випадку ми хочемо отримати т.зв. конфігураційний дескриптор. Але справа в тому, що ми не знаємо його довжину і не можемо виділити гарантовано достатній буфер пам’яті. Тому ми для початку формуємо запит з недостатнім розміром буфера – нам у відповідь повернуть необхідну довжину буфера:

pconfigdesc = exallocatepool(nonpagedpool, sizeof(usb_configuration_descriptor) );

usbbuildgetdescriptorrequest(purb,

(ushort) sizeof (struct _urb_control_descriptor_request), usb_configuration_descriptor_type, 0, 0, pconfigdesc, null, sizeof(usb_configuration_descriptor), null);

pirp = iobuilddeviceiocontrolrequest(ioctl_internal_usb_submit_urb,

pdevext ->topstackdevice, null, 0, null, 0, true, &event, &iostatus);

nextstack = iogetnextirpstacklocation(pirp);

nextstack->parameters.others.argument1 = urb;

iocalldriver(pdevext ->topstackdevice, pirp);

А ось тепер спробуємо отримати актуальну конфігураційну інформацію:

pdevext ->usbconfigdescriptor = exallocatepool(nonpagedpool, pconfigdesc ->wtotallength);

usbbuildgetdescriptorrequest(purb, (ushort) sizeof (struct _urb_control_descriptor_request),
usb_configuration_descriptor_type, 0, 0, pdevext ->usbconfigdescriptor, null,
pconfigdesc ->wtotallength, null);

nextstack = iogetnextirpstacklocation(pirp);

nextstack->parameters.others.argument1 = urb;

iocalldriver(pdevext ->topstackdevice, pirp);

kdprint((“usblink configuration descriptor:n”));

kdprint((“————————-n”));

kdprint((“bdescriptortype 0x%xn”, pdevext ->usbconfigdescriptor->bdescriptortype));

kdprint((“wtotallength 0x%xn”, pdevext ->usbconfigdescriptor-> wtotallength));

kdprint((“bnuminterfaces 0x%xn”, pdevext ->usbconfigdescriptor->bnuminterfaces));

kdprint((“iconfiguration 0x%xn”, pdevext ->usbconfigdescriptor->iconfiguration));

kdprint((“bmattributes 0x%xn”, pdevext ->usbconfigdescriptor->bmattributes));

kdprint((“maxpower 0x%xn”, pdevext ->usbconfigdescriptor->maxpower));
exfreepool(pconfigdesc);
exfreepool(purb);

Якщо читач стомився розглядом коду – можу тільки поспівчувати – далі починається найголовніше.

Отримавши конфігураційний описувач (дескриптор) отримаємо список інтерфейсів, що надаються пристроєм. Цим займається usbd.sys. А ми будемо використовувати функції, що експортуються цим драйвером (драйвера, як будь-які pe файли можуть експортувати функції, чого тут дивного? j).

pusbd_interface_list_entry pinterfaceslist;
pusb_interface_descriptor pcurrentdescriptor;
ulong i;

pinterfaceslist = exallocatepool(nonpagedpool, sizeof(usbd_interface_list_entry)*
(pdevext ->usbconfigdescriptor -> bnuminterfaces + 1) );

for (i = 0; i < pdevext ->usbconfigdescriptor ->bnuminterfaces; i++ )
{
pcurrentdescriptor = usbd_parseconfigurationdescriptorex(pdevext->usbconfigdescriptor,
pdevext->usbconfigdescriptor, i, 0, -1, -1, -1 );

pinterfaceslist[i].interfacedescriptor = pcurrentdescriptor;

pinterfaceslist[i].interface = null;

}

pinterfaceslist[i].interfacedescriptor = null;
pinterfaceslist[i].interface = null;
purb = usbd_createconfigurationrequestex(pdevext ->usbconfigdescriptor, pinterfaceslist);

for (i = 0; i < pinterfaceslist[0].interface->numberofpipes; ++i)
pinterfaceslist[0].interface -> pipes[i].maximumtransfersize = max_transfer_size;

usbbuildselectconfigurationrequest(purb, sizeof(struct _urb_select_configuration),
pdevext->usbconfigdescriptor);

pirp = iobuilddeviceiocontrolrequest(ioctl_internal_usb_submit_urb,
pdevext ->topstackdevice, null, 0, null, 0, true, &event, &iostatus);

nextstack = iogetnextirpstacklocation(pirp);
nextstack->parameters.others.argument1 = urb;

iocalldriver(pdevext ->topstackdevice, pirp);

Я чесно кажучи втомився коментувати кожну строчку коду, так що займіться цим самі j. Замість цього я краще зверну вашу увагу на наступний факт: отримавши конфігураційний дескриптор ми знали число інтерфейсів, наданих нам пристроєм (bnuminterfaces). А виділили пам’яті на один елемент списку більше. Навіщо? Справа в тому, що у функції немає параметра задає довжину списку usbd_createconfigurationrequestex. Натомість довжина списку визначається на манер роботи з нуль-термінірованние рядками – останнім варто елемент, у якого pinterfaceslist [i]. Interfacedescriptor = null;

Після обробки запиту, сформованого за допомогою usbd_createconfigurationrequestex для кожного елемента списку інтерфейсів ми отримаємо дещо цікаве: pinterfaceslist [num]. Interface -> pipes – список пайпов даного інтерфейсу (масив структур usbd_pipe_information). Для нас у цій структурі найважливішим буде поле usbd_pipe_handle pipehandle – коли ми захочемо щось заслати в пайпу, потрібно буде створити відповідний urb запит urb_bulk_or_interrupt_transfer і вказати цей самий дескриптор пайпа. Як раз і розглянемо, як що-небудь записати в пайп. Запис проводиться або за ініціативою драйвера, або з ініціативи клієнта драйвера – при обробці запиту irp_mj_write.

ntstatus ntstatus = status_success;

pdevice_extension pdevext;

pio_stack_location pirpstack, pnextstack;

ulong length = 0;

purb purb = null, pprevurb = null, pfirsturb = null;

pmdl pmdl;

pdevext = deviceobject -> deviceextension;

pirpstack = iogetcurrentirpstacklocation (irp);

if (irp->mdladdress) length = mmgetmdlbytecount(irp->mdladdress);

purb = exallocatepool(nonpagedpool,

sizeof(struct _urb_bulk_or_interrupt_transfer) );

rtlzeromemory(purb, sizeof(struct _urb_bulk_or_interrupt_transfer));

purb ->urbbulkorinterrupttransfer.hdr.length = (ushort) sizeof(struct _urb_bulk_or_interrupt_transfer);

purb ->urbbulkorinterrupttransfer.hdr.function = urb_function_bulk_or_interrupt_transfer;

purb ->urbbulkorinterrupttransfer.pipehandle = pdevext ->writepipehandle;

purb ->urbbulkorinterrupttransfer.transferflags = usbd_transfer_direction_in ;

purb ->urbbulkorinterrupttransfer.transferflags /= usbd_short_transfer_ok;

purb ->urbbulkorinterrupttransfer.urblink = null;

purb ->urbbulkorinterrupttransfer.transferbuffermdl = irp->mdladdress;

purb->urbbulkorinterrupttransfer.transferbufferlength = length;

pnextstack = iogetnextirpstacklocation(irp);

pnextstack->parameters.others.argument1 = purb;

pnextstack->majorfunction = irp_mj_internal_device_control;

pnextstack->parameters.deviceiocontrol.iocontrolcode = ioctl_internal_usb_submit_urb;

iocalldriver(pdevext ->topstackdevice, irp );

Якщо ви ознайомилися уважно з кодом, наведеними в цьому розділі, то лістинг функції, що виробляє запис в пристрій не повинен бути зовсім вже незрозумілим. Звернемо увагу на деталі:

mdladdress – це вказівник на структуру mdl (memory description list), що описує буфер, переданий через irp c кодом irp_mj_write. Як уже згадувалося вище, така поведінка характерна для драйверів з прямою моделлю вводу-виводу. Повторюся, але тим не менш зауважу: при читанні даних це призводить до необхідності попереджувального дзвінка readfile – в іншому випадку, дані не будуть буферізірованний і просто буде втрачено.

На цьому ми закінчимо з розглядом нутрощів usb драйвера. У наступному розділі розглянемо взаємодію користувача додатки з драйвером.

4. Взаємодія користувача додатки з драйвером usb пристрою

Користувацька додаток взаємодіє з пристроєм опосередковано – через драйвер цього пристрою. Драйвер, як ми пам’ятаємо з попереднього розділу, реєструє і дозволяє інтерфейс, після чого система сама створює відповідне символічне ім’я, через яке можна звернутися до пристрою, як до файлу. Насамперед, це символічне ім’я треба з’ясувати. Для цього нам знадобитися бібліотека, доступна для додатків – setupapi.dll. Опис функцій, що експортуються цієї бібліотекою, є в ddk (але немає в sdk!). Розглянемо код, що дозволяє отримати доступ до драйвера пристрою як до файлу. Спочатку потрібно отримати описувач класу пристрою:

hdevinfo hdevinfo = setupdigetclassdevs ( (guid*)& guid_class_usb_drv, null, null, digcf_present / digcf_interfacedevice );

Ми попросили повернути нам описувач для пристроїв, що надають інтерфейс з guid = guid_class_usb_drv і присутніх в даний момент в системі.

Далі отримуємо коротку інформацію для інтерфейсів (в даному випадку, для першого інтерфейсу в списку з відповідним guid):

psp_device_interface_data devinfodata =

(psp_device_interface_data)malloc(sizeof(sp_device_interface_data));

devinfodata ->cbsize = sizeof(sp_device_interface_data);

setupdienumdeviceinterfaces(hdevinfo, null, (guid*)&guid_class_usb_link, 0,

devinfodata);

Після того, можна спробувати дізнатися символічне ім’я для пристрою із заданим інтерфейсом. Але для цього спочатку визначимо необхідну для зберігання цього імені довжину буфера:

ulong requiredlength;

setupdigetinterfacedevicedetail (hdevinfo, devinfodata, null, 0, &requiredlength, null);

А тепер можна нарешті дізнатися ім’я, присвоєне пристрою:

psp_device_interface_detail_data devinfodetail =

(psp_device_interface_detail_data)malloc(requiredlength);

devinfodetail ->cbsize = sizeof(sp_device_interface_detail_data);

setupdigetinterfacedevicedetail (hdevinfo, devinfodata, devinfodetail,

requiredlength, &requiredlength, null);

Після цього, відкриваємо пристрій як файл:

husbdevice = createfile ( devinfodetail->devicepath, generic_read / generic_write, file_share_read / file_share_write, null, open_existing, 0, null);

Після цього, з пристроєм можна працювати як зі звичайним файлом: використовуючи функції readfile (ex), writefile (ex), deviceiocontrol.

Зверніть увагу! Бібліотека setupapi.dll вимагає, щоб структури, які передаються в неї, мали однобайтовое вирвніваніе. Перевірте опції компілятора (за замовчуванням, швидше за все, вирівнювання буде іншим).

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*