Журнал LinuxFormat - перейти на главную

LXF138:driver

Материал из Linuxformat
Перейти к: навигация, поиск
Боль­шой про­ект Возь­мем USB-уст­рой­ст­во и на­пи­шем для не­го Linux-драй­вер

Содержание

USB: Драй­вер свои­ми ру­ка­ми

Linux не под­дер­жи­ва­ет нуж­ную вам пе­ри­фе­рию? Как и все в сво­бод­ном ПО, это де­фект мож­но ис­пра­вить са­мо­стоя­тель­но. Ан­д­рей Бо­ров­ский рас­смот­рит про­цесс от и до.

Хо­тим мы то­го или нет, но мно­го­чис­лен­ные пор­ты, унас­ле­до­ван­ные от IBM PC и PS/2, ухо­дят в про­шлое. Бу­ду­щее при­над­ле­жит универ­саль­ным ско­ро­ст­ным пор­там ти­па USB и Firewire. Об удоб­ствах, ко­то­рые USB пре­достав­ля­ет про­стым поль­зо­ва­те­лям ПК, рас­про­стра­нять­ся не при­хо­дит­ся. Еди­ный ин­тер­фейс для всех уст­ройств, об­ла­даю­щий воз­мож­но­стя­ми Plug’n’Play и про­дви­ну­то­го управ­ления пи­танием – имен­но то, что нуж­но лю­дям, для ко­то­рых ком­пь­ю­тер – часть бы­то­вой тех­ники. Дру­гое де­ло – ин­ди­ви­ду­аль­ные раз­ра­бот­чи­ки раз­лич­ных уст­ройств и про­сто ха­ке­ры. Для этих ка­те­го­рий пе­ре­ход на USB пред­став­ля­ет оп­ре­де­лен­ные слож­но­сти. Про­бле­ма за­клю­ча­ет­ся в том, что USB – «ин­тел­лек­ту­аль­ный» ин­тер­фейс. Лю­бое уст­рой­ство, пред­на­зна­чен­ное для под­клю­чения к ком­пь­ю­те­ру че­рез USB, долж­но под­дер­жи­вать хо­тя бы неболь­шую часть спе­ци­фи­ка­ции про­то­ко­ла USB: уметь «пред­ста­вить­ся» (со­об­щить ин­фор­ма­цию о се­бе и сво­их воз­мож­но­стях) и аде­к­ват­но реа­ги­ро­вать на стан­дарт­ные со­об­щения USB, по­сы­лае­мые ком­пь­ю­те­ром. В ре­зуль­та­те да­же уст­рой­ство, все функ­ции ко­то­ро­го ог­раничи­ва­ют­ся вклю­чением и вы­клю­чением све­то­дио­да по сиг­на­лу с ком­пь­ю­те­ра, при под­клю­чении че­рез USB тре­бу­ет на­ли­чия мик­ро­схе­мы, ко­то­рая уме­ет «раз­го­ва­ри­вать» с хостом.

Од­на­ко и для раз­ра­бот­чи­ков соб­ствен­ных уст­ройств пе­ре­ход на USB несет оп­ре­де­лен­ные пре­иму­ще­ства. Пре­ж­де все­го, уп­ро­ща­ет­ся про­цесс напи­сания драй­ве­ров. По­сколь­ку для об­щения с ком­пь­ю­те­ром все USB-уст­рой­ства ис­поль­зу­ют один про­то­кол, аб­ст­ра­ги­ро­ван­ный от та­ких ап­па­рат­но-за­ви­си­мых ве­щей, как ото­бра­жен­ные в па­мять пор­ты и пре­ры­вания, возника­ет воз­мож­ность не пи­сать для ка­ж­до­го уст­рой­ства свой соб­ствен­ный драй­вер уров­ня яд­ра. Вме­сто это­го це­лые груп­пы уст­ройств мо­гут ис­поль­зо­вать один и тот же драй­вер уров­ня яд­ра, а спе­ци­фич­ный код, учи­ты­ваю­щий осо­бен­но­сти кон­крет­но­го уст­рой­ства, мо­жет быть раз­ме­щен в про­стран­стве поль­зо­ва­те­ля. При этом драй­вер уров­ня яд­ра бе­рет на се­бя та­кие функ­ции, как управ­ление пи­танием уст­рой­ства (весь­ма нетри­ви­аль­ная за­да­ча, при усло­вии, что сам ком­пь­ю­тер мо­жет пе­ре­клю­чать­ся ме­ж­ду несколь­ки­ми энер­го­сбе­ре­гаю­щи­ми ре­жи­ма­ми), остав­ляя нам са­мое ин­те­рес­ное – управ­ление функ­ция­ми уст­рой­ства.

Пе­ре­нос ко­да управ­ления уст­рой­ством в про­стран­ство поль­зо­ва­те­ля не толь­ко уп­ро­ща­ет от­лад­ку (при па­дении при­ло­жения, ско­рее все­го, не при­дет­ся пе­ре­за­гру­жать ма­ши­ну), но и по­зво­лит пи­сать про­це­ду­ры управ­ления уст­рой­ством на са­мых раз­ных язы­ках про­грам­ми­ро­вания, а не толь­ко на C. Бо­лее то­го, поль­зо­ва­тель­ская часть драй­ве­ра, не взаи­мо­дей­ствую­щая на­пря­мую с ме­ханиз­ма­ми яд­ра ОС, мо­жет быть сде­ла­на кросс-плат­фор­мен­ной, что мы и име­ем в слу­чае та­ких ин­ст­ру­мен­тов как libusb и Jungo WinDriver. Бла­го­да­ря по­следним, об­хо­дить­ся без соб­ствен­ных драй­ве­ров яд­ра мо­гут да­же мно­гие уст­рой­ства про­мыш­лен­но­го уров­ня. Что уж го­во­рить о лю­би­тель­ских?

Про­то­кол USB

Про­то­кол USB по­хож на стек TCP/IP (ко­то­рый от­части и по­слу­жил его про­то­ти­пом). Как и в слу­чае с се­те­вы­ми про­то­ко­ла­ми, USB мож­но раз­де­лить на несколь­ко уровней. На са­мом нижнем ло­ги­че­ском уровне (спе­ци­фи­ка­ции фи­зи­че­ско­го уров­ня мы не рас­смат­ри­ва­ем) уст­рой­ства об­менива­ют­ся па­ке­та­ми дан­ных (со встро­ен­ны­ми ме­ханиз­ма­ми кор­рек­ции оши­бок, под­твер­ждения по­лу­чения и т. д). Из па­ке­тов фор­ми­ру­ют­ся за­про­сы, ко­то­рые уст­рой­ства по­сы­ла­ют друг дру­гу. За­про­сы со­став­ля­ют бло­ки за­про­сов USB (USB Request Block, URB).

Про­грам­мист, ко­то­рый пи­шет драй­вер уст­рой­ства USB, мо­жет не за­бо­тить­ся о де­та­лях пе­ре­да­чи от­дель­ных па­ке­тов – эти­ми ве­ща­ми управ­ля­ют ме­ханиз­мы бо­лее низ­ко­го уров­ня. Хо­тя понимание ра­бо­ты про­то­ко­лов USB на низ­ком уровне мо­жет быть по­лез­но, в на­шей ста­тье для его опи­сания не хва­тит места. Рас­смот­рим толь­ко то, что необ­хо­ди­мо раз­ра­бот­чи­кам драй­ве­ров.

Про­то­кол USB яв­ля­ет­ся «хост-цен­трич­ным» – про­цесс пе­ре­да­чи дан­ных все­гда иниции­ру­ет­ся хостом (то есть ком­пь­ю­те­ром). Ес­ли у пе­ри­фе­рий­но­го уст­рой­ства поя­ви­лись дан­ные для пе­ре­да­чи хосту, оно долж­но ожи­дать за­про­са хоста на пе­ре­да­чу дан­ных. Су­ще­ству­ет че­ты­ре ти­па пе­ре­дач дан­ных:

  • Пе­ре­да­ча управ­ляю­щих дан­ных [control transfer] пред­на­зна­че­на для оп­ре­де­ления па­ра­мет­ров и на­строй­ки пе­ри­фе­рий­ных уст­ройств, а так­же для пе­ре­да­чи ко­рот­ких команд. По­лез­ная часть бло­ка управ­ляю­щих дан­ных со­сто­ит из уста­но­воч­но­го па­ке­та [setup packet] и (воз­мож­но) несколь­ких бай­тов дан­ных. Уста­но­воч­ный па­кет со­дер­жит ин­фор­ма­цию о за­про­се, ко­то­рый хост на­прав­ля­ет уст­рой­ству, на­прав­лении пе­ре­да­чи до­полнитель­ных дан­ных (от хоста к уст­рой­ству или на­обо­рот), ло­ги­че­ском ад­ре­са­те дан­ных (уст­рой­ство, ин­тер­фейс) и ко­ли­че­стве байт до­полнитель­ных дан­ных.
  • Пе­ре­да­ча пре­ры­ваний [interrupt transfer] – их не сле­ду­ет пу­тать с пре­ры­вания­ми в ком­пь­ю­те­ре – ис­поль­зу­ет­ся для ко­рот­ких со­об­щений, в основ­ном от уст­рой­ства хосту. По­сколь­ку инициа­ти­ва в об­мене дан­ны­ми все­гда ис­хо­дит от хоста, пре­ры­вания, по­сы­лае­мые уст­рой­ством, не мо­гут пре­рвать по­ря­док ра­бо­ты хоста. Од­на­ко уст­рой­ство ожи­да­ет, что хост бу­дет оп­ра­ши­вать его на пред­мет пре­ры­ваний с оп­ре­де­лен­ной часто­той (она оп­ре­де­ля­ет­ся в про­цес­се на­строй­ки со­единения). Та­ким об­ра­зом, уст­рой­ство мо­жет рас­счи­ты­вать, что за­держ­ка при пе­ре­да­че пре­ры­ваний не пре­вы­сит оп­ре­де­лен­но­го зна­чения. При этом ко­ли­че­ство дан­ных, пе­ре­да­вае­мое в од­ном пре­ры­вании, ог­раниче­но (8, 64 или 1024 бай­та­ми, в за­ви­си­мо­сти от ско­ро­ст­ных па­ра­мет­ров уст­рой­ства). Сле­ду­ет учи­ты­вать, что «га­ран­ти­ро­ван­ное мак­си­маль­ное вре­мя за­держ­ки» га­ран­ти­ро­ва­но толь­ко для достав­ки пре­ры­вания хосту. Фак­ти­че­ская об­ра­бот­ка вы­пол­ня­ет­ся ПО и мо­жет быть от­ло­же­на на неоп­ре­де­лен­ное вре­мя.
  • Изо­хрон­ная пе­ре­да­ча дан­ных [isochronous transfer] ис­поль­зу­ет­ся для муль­ти­ме­диа, где важ­на га­ран­ти­ро­ван­ная про­пу­ск­ная спо­соб­ность, но по­те­ря от­дель­ных па­ке­тов из-за по­мех несу­ще­ствен­на (ошиб­ки вы­яв­ля­ют­ся, но по­втор­ная от­прав­ка сбой­ных па­ке­тов не про­из­во­дит­ся). Изо­хрон­ная пе­ре­да­ча воз­мож­на в обо­их на­прав­лениях. Обыч­но этим спо­со­бом пе­ре­да­ют­ся жи­вые муль­ти­ме­диа-дан­ные (на­при­мер, ви­део, по­лу­чае­мое с циф­ро­вой ка­ме­ры в ре­жи­ме он­лайн).
  • Мас­со­вая пе­ре­да­ча дан­ных [bulktransfer] – это пе­ре­да­ча боль­ших объ­е­мов дан­ных с га­ран­ти­ро­ван­ной достав­кой, но нега­ран­ти­ро­ван­ной мак­си­маль­ной за­держ­кой и по­ло­сой про­пускания. При­ме­ром та­кой пе­ре­да­чи дан­ных мо­гут слу­жить дан­ные, пе­ре­да­вае­мые ком­пь­ю­те­ром прин­те­ру, или дан­ные, ко­то­ры­ми хост об­менива­ет­ся с уст­рой­ством хранения. Мас­со­вая пе­ре­да­ча дан­ных так­же воз­мож­на в обо­их на­прав­лениях.

Про­дол­жая ана­ло­гию ме­ж­ду USB и се­те­вы­ми про­то­ко­ла­ми, мы мо­жем вспомнить, что «пункт на­зна­чения» TCP/IP вклю­ча­ет по­ми­мо ад­ре­са еще и порт. Его ана­ло­гом в USB яв­ля­ет­ся конеч­ная точ­ка [endpoint]. Ка­ж­дое уст­рой­ство USB под­дер­жи­ва­ет конеч­ную точ­ку с но­ме­ром 0x00, пред­на­зна­чен­ную для пе­ре­да­чи управ­ляю­щих дан­ных. По­ми­мо это­го, уст­рой­ство мо­жет пре­достав­лять еще несколь­ко конеч­ных то­чек, пред­на­зна­чен­ных для оп­ре­де­лен­но­го ти­па пе­ре­да­чи дан­ных в оп­ре­де­лен­ном на­прав­лении (за исклю­чением точ­ки 0x00, ка­ж­дая кон­крет­ная конеч­ная точ­ка мо­жет пе­ре­да­вать дан­ные толь­ко в од­ном на­прав­лении). На­при­мер, ес­ли уст­рой­ству тре­бу­ет­ся принимать дан­ные с по­мо­щью мас­со­вой пе­ре­да­чи и пе­ре­да­вать пре­ры­вания, оно пре­доста­вит две до­полнитель­ных конеч­ных точ­ки. По­ми­мо на­прав­ления пе­ре­да­чи дан­ных, в опи­сании конеч­ной точ­ки ука­зы­ва­ет­ся мак­си­маль­ный раз­мер пе­ре­да­вае­мо­го па­ке­та в бай­тах. Груп­пы конеч­ных то­чек уст­рой­ства объ­е­ди­ня­ют­ся в ин­тер­фей­сы. Де­ск­рип­тор ин­тер­фей­са со­дер­жит иден­ти­фи­ка­тор клас­са уст­рой­ства (HID, Mass Storage и т. д.), бла­го­да­ря че­му од­но и то же фи­зи­че­ское уст­рой­ство мо­жет пре­достав­лять ин­тер­фей­сы раз­лич­ных клас­сов. Ин­тер­фей­сы объ­е­ди­ня­ют­ся в кон­фи­гу­ра­ции, ко­то­рые, по­ми­мо про­че­го, вклю­ча­ют опи­сания ре­жи­мов пи­тания уст­рой­ства. Та­ким об­ра­зом, од­но фи­зи­че­ское уст­рой­ство мо­жет воспринимать­ся систе­мой как несколь­ко раз­ных уст­ройств USB.

У боль­шин­ства уст­ройств при­сут­ству­ет ин­тер­фейс, со­стоя­щий из двух то­чек досту­па: для пе­ре­да­чи управ­ляю­щих со­об­щений и для пе­ре­да­чи пре­ры­ваний от уст­рой­ства хосту. Ес­ли, по­ми­мо пе­ре­да­чи управ­ляю­щей ин­фор­ма­ции, уст­рой­ству нуж­но пе­ре­да­вать боль­шие объ­е­мы дан­ных в двух на­прав­лениях, оно мо­жет пре­доста­вить еще один ин­тер­фейс, объ­е­ди­няю­щий две точ­ки досту­па, пред­на­зна­чен­ные для мас­со­вой пе­ре­да­чи дан­ных, и так да­лее.

Са­мо уст­рой­ство иден­ти­фи­ци­ру­ет­ся дву­мя чис­ла­ми: иден­ти­фи­ка­то­ром про­из­во­ди­те­ля VID и иден­ти­фи­ка­то­ром про­дук­та PID. С точ­ки зрения систе­мы уст­рой­ство иден­ти­фи­ци­ру­ет­ся ад­ре­сом на шине USB и эти­ми дву­мя чис­ла­ми. Та­ким об­ра­зом, на­строй­ка свя­зи драй­ве­ра с уст­рой­ством вклю­ча­ет по­иск уст­рой­ства с за­дан­ны­ми VID и PID на шине USB, по­сле че­го драй­вер вы­би­ра­ет кон­фи­гу­ра­цию, ин­тер­фейс и груп­пу конеч­ных то­чек, ес­ли вы­бран­ный ин­тер­фейс под­дер­жи­ва­ет несколь­ко групп. Все это вы­гля­дит слож­но, но, к сча­стью, у нас под ру­кой есть ути­ли­ты, ко­то­рые все­гда под­ска­жут нам, что имен­но вклю­ча­ет на­строй­ка уст­рой­ства.

Све­дения о ло­ги­че­ской струк­ту­ре уст­рой­ства нам по­мо­жет по­лу­чить ути­ли­та lsusb. Про­стой вы­зов lsusb пе­ре­чис­лит ад­ре­са под­клю­чен­ных к шине USB уст­ройств и их па­ры VID и PID. Вот как мо­жет вы­гля­деть вы­да­ча коман­ды lsusb, вы­зван­ной без па­ра­мет­ров:

Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 002: ID 80ee:0021
Bus 002 Device 012: ID 1d34:0004

Чис­ла, сле­дую­щие за ID, пред­став­ля­ют со­бой па­ры VID:PID для дан­но­го уст­рой­ст­ва. Ес­ли те­перь мы хо­тим по­лу­чить под­роб­ные све­де­ния об уст­рой­ст­ве 1d34:0004, ко­ман­ду­ем:

lsusb -v -d 1d34:0004

Фраг­мент вы­да­чи ко­ман­ды при­во­дит­ся ни­же


Device Descriptor:
...
idVendor 0x1d34
idProduct 0x0004
bcdDevice 0.02
iManufacturer 1 Dream Link
iProduct 2 DL100B Dream Cheeky Generic Controller
bNumConfigurations 1
Configuration Descriptor:
...
bNumInterfaces 1
Interface Descriptor:
….
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
...
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10
Device Status: 0x0000
(Bus Powered)

Из это­го фраг­мен­та мы уз­на­ем, что уст­рой­ство под­дер­жи­ва­ет од­ну кон­фи­гу­ра­цию (по­ле bNumConfigurations) и один ин­тер­фейс с од­ной до­полнитель­ной конеч­ной точ­кой (по­ле bNumEndpoints; точ­ка 0x00 не учи­ты­ва­ет­ся, по­сколь­ку при­сут­ству­ет все­гда). Эта конеч­ная точ­ка име­ет ад­рес 0x81 и пред­на­зна­че­на для пе­ре­да­чи пре­ры­ваний от уст­рой­ства хосту.

Обо­ру­до­вание

Уст­рой­ство, с ко­то­рым мы по­зна­ко­ми­лись та­ким необыч­ным об­ра­зом – это неболь­шая без­де­луш­ка про­из­вод­ства ком­пании Dream Cheeky (http://www.dreamcheeky.com), из­вест­ной свои­ми USB-ра­кетница­ми, по­дог­ре­ва­те­ля­ми ко­фе и дру­ги­ми столь же по­лез­ны­ми из­де­лия­ми. Рас­смат­ри­вае­мое уст­рой­ство (рис. 1) по­зи­циониру­ет­ся ком­панией как ин­ди­ка­тор по­сту­п­ления элек­трон­ной поч­ты. Оно пред­став­ля­ет со­бой пла­сти­ко­вую ко­ро­боч­ку с изо­бра­жением кон­вер­та, ко­то­рая под­све­чи­ва­ет­ся из­нут­ри с по­мо­щью ком­би­на­ции трех све­то­дио­дов: крас­но­го, синего и зе­ле­но­го (по­сколь­ку ка­ж­дый све­то­ди­од об­ла­да­ет 256 гра­да­ция­ми яр­ко­сти, мы име­ем воз­мож­ность вы­би­рать цвет из 24‑бит­ной па­лит­ры). В ком­плек­те с уст­рой­ством идет Windows-про­грам­ма, ко­то­рая уме­ет оп­ра­ши­вать со­стояние ука­зан­ных поль­зо­ва­те­лем поч­то­вых ящи­ков и вы­да­вать оп­ре­де­лен­ные све­то­вые эф­фек­ты при по­сту­п­лении поч­ты.

С мо­ей точ­ки зрения, Dream Cheeky Webmail Notifier пред­став­ля­ет со­бой яр­кий при­мер «же­ле­за», воз­мож­но­сти ко­то­ро­го искусствен­но ог­раниче­ны со­пут­ствую­щим ПО. Хо­тя под­све­чи­вание пла­сти­ка раз­но­цвет­ны­ми све­то­дио­да­ми и нель­зя на­звать бо­га­той функ­цио­наль­но­стью, у уст­рой­ства мо­жет быть го­раз­до боль­ше за­бав­ных и да­же по­лез­ных при­менений, чем пред­ла­га­ет про­из­во­ди­тель (а его мож­но ис­поль­зо­вать, на­при­мер, для ин­ди­ка­ции со­стояния ком­пь­ю­те­ра, к ко­то­ро­му не под­клю­чен мони­тор). Все, что для это­го нуж­но – ра­зо­брать­ся в ра­бо­те уст­рой­ства и напи­сать для него свою про­грам­му управ­ления.

Об­рат­ный ин­жиниринг

Важней­шим ин­ст­ру­мен­том при напи­сании драй­ве­ра для но­во­го USB-уст­рой­ства яв­ля­ет­ся USB-сниф­фер. Так же, как се­те­вые сниф­фе­ры пе­ре­хва­ты­ва­ют се­те­вые па­ке­ты и по­зво­ля­ют под­смат­ри­вать их со­дер­жи­мое, USB-сниф­фе­ры пе­ре­хва­ты­ва­ют па­ке­ты USB. Сниф­фе­ры бы­ва­ют про­грамм­ные и ап­па­рат­ные. Мы, есте­ствен­но, со­сре­до­то­чим­ся на пер­вой ка­те­го­рии.

Ор­ганизо­вать «пер­лю­ст­ра­цию» USB-па­ке­тов в систе­ме Linux очень лег­ко. С неза­па­мят­ных вре­мен яд­ро вклю­ча­ет мо­дуль usbmon, ко­то­рый, соб­ствен­но, этим и занима­ет­ся. Для под­клю­чения мо­ду­ля usbmon коман­ду­ем:

modprobe usbmon

Те­перь мы мо­жем про­смат­ри­вать USB-тра­фик с по­мо­щью ко­ман­ды cat, на­при­мер:

cat /dev/usbmon1


Од­на­ко со­дер­жи­мое спе­ци­аль­ных фай­лов, соз­дан­ных usbmon, труд­но­чи­тае­мо. К сча­стью, у нас есть очень мощ­ный ин­ст­ру­мент – про­грам­ма Wireshark (рис. 2). Тра­ди­ци­он­но она при­ме­ня­ет­ся для ана­ли­за се­те­во­го тра­фи­ка, од­на­ко с неко­то­рых пор (вер­сия 1.2 и вы­ше) уме­ет чи­тать и па­ке­ты USB. Что­бы Wireshark смог чи­тать тра­фик, генери­руе­мый usbmon, сле­ду­ет под­мон­ти­ро­вать спе­ци­аль­ные фай­ло­вые систе­мы

mount -t usbfs /dev/bus/usb /proc/bus/usb

В спи­ске на­блю­дае­мых ин­тер­фей­сов Wireshark мы мо­жем вы­брать ин­тер­фейс USB X.Y, где X.Y – ад­рес ин­те­ре­сую­ще­го нас уст­рой­ства на шине USB (а уз­нать, ка­кой ад­рес по­лу­чи­ло ин­те­ре­сую­щее нас уст­рой­ство, мож­но с по­мо­щью lsusb). От­ме­чу неболь­шой «глюк», с ко­то­рым я столк­нул­ся при ра­бо­те с тра­фи­ком USB в Wireshark 1.2.1: фильтр, ко­то­рый про­грам­ма по умол­чанию при­ме­ня­ет к ин­тер­фей­су USB, рас­счи­тан на па­ке­ты TCP/IP и вы­зы­ва­ет ошиб­ки. Что­бы это ис­пра­вить, щелкните кноп­ку Capture Options... и в от­кры­вав­шем­ся окне от­ре­дак­ти­руй­те или во­об­ще очи­сти­те стро­ку Capture Filter.

Wireshark по­зво­ля­ет нам вы­би­рать уро­вень де­та­ли­за­ции ин­фор­ма­ции о па­ке­тах, за­да­вать спе­ци­аль­ные фильт­ры для от­бо­ра толь­ко ин­те­ре­сую­щих нас дан­ных, со­хра­нять ре­зуль­та­ты в раз­лич­ных фор­ма­тах, и что осо­бен­но цен­но – кор­рект­но об­ра­ба­ты­вать си­туа­ции под­клю­чения и от­клю­чения уст­ройств, во вре­мя ко­то­рых с по­следними про­ис­хо­дит мно­го ин­те­рес­но­го.

И, тем не менее, на дан­ном эта­пе сред­ства монито­рин­га па­ке­тов в ОС Linux нам не под­хо­дят. Ведь на­ша за­да­ча за­клю­ча­ет­ся в том, что­бы вы­яснить, как имен­но фир­мен­ная про­грам­ма коман­ду­ет уст­рой­ством. А про­грам­ма эта пред­на­зна­че­на для Windows. Ес­ли вы сто­ронник бес­плат­но­го ПО, мо­же­те восполь­зо­вать­ся па­ке­том USB Snoopy (сайт про­ек­та за­крыл­ся, но про­грам­му еще мож­но ска­чать на та­ких ре­сур­сах, как http://softpedia.com). Этот па­кет со­сто­ит из фильтр-драй­ве­ра USB и ути­ли­ты для управ­ления им. Для про­смот­ра ре­зуль­та­тов ис­поль­зу­ет­ся про­грам­ма DebugView, напи­сан­ная из­вест­ным ис­сле­до­ва­те­лем внут­рен­но­стей Windows Мар­ком Русси­но­ви­чем [Mark Russinovich]. По удоб­ству ис­поль­зо­вания USB Snoopy усту­па­ет Wireshark (кста­ти, в до­ку­мен­та­ции Wireshark ска­за­но, что Windows-вер­сия этой про­грам­мы то­же мо­жет от­сле­жи­вать тра­фик USB, но про­це­ду­ра на­строй­ки Wireshark вы­гля­дит до­воль­но слож­ной, и я ее не про­бо­вал). В ми­ре же плат­но­го ПО мне бо­лее дру­гих при­гля­ну­лась про­грам­ма Device Monitoring Studio (http://www.hhdsoftware.com). Рас­про­стра­ня­ет­ся она как shareware, сто­ит недо­ро­го, а пер­вые две неде­ли по­сле уста­нов­ки ею мож­но поль­зо­вать­ся бес­плат­но (ес­ли толь­ко вам не на­дое­да­ют на­по­ми­нания об ак­ти­ва­ции). В плане пе­ре­хва­та и ана­ли­за па­ке­тов USB про­грам­ма Device Monitoring Studio мо­жет де­лать все то, что мо­жет Wireshark+usbmon, и да­же немно­го боль­ше.

Ба­зо­вых знаний про­то­ко­ла USB вполне доста­точ­но, что­бы ра­зо­брать вы­вод про­грамм Wireshark и Device Monitoring Studio. Я ре­ко­мен­дую вам по­сле­дить за тра­фи­ком ка­ко­го-нибудь уст­рой­ства – на­при­мер, мы­ши – что­бы луч­ше по­нять, как ра­бо­та­ет USB.

Во­об­ще, хо­тя мы поль­зу­ем­ся са­мым «безо­пас­ным» спо­со­бом напи­сания драй­ве­ров, я ре­ко­мен­дую, во из­бе­жание по­терь дан­ных и вре­мени на восста­нов­ление, вы­де­лить для экс­пе­ри­ мен­тов от­дель­ный ком­пь­ю­тер. Мож­но восполь­зо­вать­ся и вир­ту­аль­ной ма­ши­ной, но на­до помнить, что эму­ля­ция USB ра­бо­та­ет все же не со­всем так, как «жи­вой» ин­тер­фейс USB, а по­то­му и ре­зуль­та­ты тести­ро­вания мо­гут немно­го от­ли­чать­ся.

Смер­тель­ный па­кет

Во вре­ме­на мо­ей ком­пь­ю­тер­ной юно­сти поль­зо­ва­те­ли (а иногда и про­грам­ми­сты) пу­га­ли друг дру­га исто­рия­ми о том, как ко­вар­ные ви­ру­сы фи­зи­че­ски раз­ру­ша­ют же­ст­кие диски, мно­го­крат­но ро­няя счи­ты­ваю­щую го­лов­ку на магнит­ную по­верх­ность. Го­во­ри­ли еще, что та­кая ка­ра мо­жет по­стичь нели­цен­зи­он­ных поль­зо­ва­те­лей Windows 95. На са­мом де­ле, по­дав­ляю­щее боль­шин­ство пе­ри­фе­рий­ных уст­ройств, пред­на­зна­чен­ных для мас­со­во­го ис­поль­зо­вания, спро­ек­ти­ро­ва­но так, что убить их слу­чай­ной ком­би­на­ци­ей бай­тов, пе­ре­дан­ных с ком­пь­ю­те­ра, прак­ти­че­ски невоз­мож­но. Ча­ще все­го уст­рой­ство воз­вра­ща­ет­ся к жизни про­стым от­клю­чением и по­втор­ным вклю­чением. Иногда, прав­да, мо­жет по­тре­бо­вать­ся про­грам­ма­тор... Но, в об­щем, экс­пе­ри­мен­ти­руй­те сме­ло. Ес­ли вы все же най­де­те коман­ду-убий­цу для по­тре­би­тель­ско­го уст­рой­ства – пи­ши­те пись­ма на фо­ру­мы и раз­ра­бот­чи­кам «же­лез­ки». Воз­мож­но, вас да­же возь­мут на ра­бо­ту.

Де­ко­ди­ру­ем про­то­кол

Рас­смот­рим фраг­мент вы­да­чи про­грам­мы Device Monitoring Studio:

000006: Class-Specific Request (DOWN), 18.09.2010 11:40:17.171 +0.0
Destination: Interface, Index 0
Reserved Bits: 34
Request: 0x9
Value: 0x200
Send 0x8 bytes to the device
00 00 02 02 00 01 14 05

Мы пе­ре­хва­ти­ли спе­ци­фич­ный для клас­са уст­рой­ства за­прос с пе­ре­да­чей дан­ных от хоста уст­рой­ству (DOWN). Точ­ные вре­мен­ные па­ра­мет­ры по­зво­ля­ют от­сле­жи­вать за­держ­ки ме­ж­ду коман­да­ми USB. На­зна­чением за­про­са яв­ля­ет­ся ин­тер­фейс, ин­декс 0. Но­мер за­про­са – 0x9, зна­чение – 0x200, вслед за уста­но­воч­ным па­ке­том пе­ре­да­ет­ся 8 бай­тов дан­ных (они при­ве­де­ны в по­следней стро­ке).

Ис­сле­до­вания тра­фи­ка USB на­ше­го поч­то­во­го ин­ди­ка­то­ра вы­яви­ли, что хост об­ра­ща­ет­ся к уст­рой­ству по­сред­ством кон­троль­ных за­про­сов, спе­ци­фич­ных для клас­са уст­рой­ства, с но­ме­ром за­про­са 0x9 и зна­чением 0x200. Вслед за уста­но­воч­ным па­ке­том хост пе­ре­да­ет од­ну из 8‑байт­ных команд.

В на­ча­ле ра­бо­ты уст­рой­ст­ву по­сы­ла­ют­ся ко­ман­ды

0x1F 0x1E 0x92 0x7C 0xB8 0x01 0x14 0x03
0x00 0x1E 0x92 0x7C 0xB8 0x01 0x14 0x04

Это, су­дя по все­му, «вол­шеб­ные чис­ла», ко­то­рые необ­хо­ди­мы для инициа­ли­за­ции уст­рой­ства. Воз­мож­но, из­менения ка­ких-то па­ра­мет­ров этих команд влия­ют на па­ра­мет­ры инициа­ли­за­ции уст­рой­ства – я не про­ве­рял. Управ­ление све­то­дио­да­ми осу­ще­ст­в­ля­ет коман­да

R G B ? ? 0x01 0x14 0x05

Пер­вые три бай­та – зна­чения яр­ко­сти трех све­то­дио­дов. Что де­ла­ют бай­ты 4 и 5, я так и не вы­яснил. На­блю­дения за тра­фи­ком по­ка­зы­ва­ют, что фир­мен­ная про­грам­ма иногда запи­сы­ва­ет в них ка­кие-то зна­чения, но внешне это никак не влия­ет на ра­бо­ту уст­рой­ства.

В от­вет на коман­ду с конеч­ной точ­ки 0x01 уст­рой­ство по­сы­ла­ет хосту пре­ры­вание – 8 байт, ко­то­рые, су­дя по все­му, име­ют зна­чения

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01

ес­ли ко­ман­да вы­пол­не­на, и

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

в про­тив­ном слу­чае.

Во­об­ще, об­рат­ный ин­жиниринг – хо­ро­ший тест на IQ. Помните за­дания, в ко­то­рых пред­ла­га­ет­ся уга­дать недостаю­щие или про­пу­щен­ные чле­ны чи­сло­вой по­сле­до­ва­тель­но­сти? При ана­ли­зе тра­фи­ка USB нам часто при­хо­дит­ся занимать­ся тем же са­мым.

На­при­мер, ана­ли­зи­руя при­ве­ден­ные вы­ше коман­ды, мож­но пред­по­ло­жить, что уст­рой­ство под­дер­жи­ва­ет так­же коман­ды

? ? ? ? ? 0x01 0x14 0x00
? ? ? ? ? 0x01 0x14 0x01
? ? ? ? ? 0x01 0x14 0x02

Я не про­ве­рял, су­ще­ству­ют ли та­кие коман­ды в дей­стви­тель­но­сти, и тем бо­лее, что они де­ла­ют. Ес­ли поч­то­вый ин­ди­ка­тор по­па­дет к вам в ру­ки – мо­же­те по­про­бо­вать са­ми.

Те­перь мы зна­ем все, что нуж­но для напи­сания соб­ствен­но­го драй­ве­ра уст­рой­ства. Оста­лось его реа­ли­зо­вать.

С че­ло­ве­че­ским ли­цом

Помните вы­вод lsusb в на­ча­ле ста­тьи? В нем есть та­кая стро­ка:

bInterfaceClass 3 Human Interface Device

Это зна­чит, что на­ша све­тя­щая­ся ко­ро­боч­ка при­над­ле­жит к клас­су HID – Human Interface Devices, то есть уст­ройств, пред­на­зна­чен­ных для непо­сред­ствен­но­го взаи­мо­дей­ствия с че­ло­ве­ком. Сре­ди уст­ройств USB класс HID яв­ля­ет­ся са­мым слож­ным и мно­го­об­раз­ным. Под­роб­но­сти вы мо­же­те уз­нать в офи­ци­аль­ной спе­ци­фи­ка­ции Device Class Definition for Human Interface Devices, ко­то­рая досту­пна по ад­ре­су http://www.usb.org/developers/devclass_docs/HID1_11.pdf (те­ку­щая вер­сия – 1.11).

Из все­го мно­го­об­ра­зия свойств HID для нас сей­час важнее все­го две ве­щи: во-пер­вых, под­держ­ка уст­ройств HID уже встро­е­на в на­шу опе­ра­ци­он­ную систе­му (будь то Linux или Windows). Яд­ро систе­мы зна­ет, как управ­лять уст­рой­ством «в це­лом», а это зна­чит, что для управ­ления его спе­ци­фи­че­ски­ми функ­ция­ми мы мо­жем ис­поль­зо­вать ин­тер­фей­сы при­клад­но­го уров­ня, оста­вив всю чер­ную ра­бо­ту яд­ру ОС.

Вто­рая важ­ная осо­бен­ность уст­ройств HID свя­за­на с тем, как они об­менива­ют­ся дан­ны­ми с ком­пь­ю­те­ром. Пред­по­ла­га­ет­ся, что уст­рой­ства это­го клас­са (мы­ши, кла­виа­ту­ры, джой­сти­ки, тек­сто­вые тер­ми­на­лы) пе­ре­да­ют и по­лу­ча­ют не очень мно­го дан­ных. Тра­ди­ци­он­ные уст­рой­ства HID не ис­поль­зу­ют мас­со­вую и изо­хрон­ную пе­ре­да­чу дан­ных. По­ми­мо стан­дарт­но­го ка­на­ла пе­ре­да­чи управ­ляю­щих со­об­щений, уст­рой­ство HID долж­но под­дер­жи­вать ка­нал пе­ре­да­чи пре­ры­ваний, на­прав­лен­ных от уст­рой­ства к хосту. Воз­мож­но, но не обя­за­тель­но, на­ли­чие ка­на­ла для пе­ре­да­чи пре­ры­ваний и в про­ти­во­по­лож­ном на­прав­лении. На­блю­дение за тра­фи­ком Dream Cheeky WebMail Notifier под управ­лением ОС Windows сви­де­тель­ству­ет о том, что уст­рой­ство ис­поль­зу­ет толь­ко управ­ляю­щий ка­нал и ка­нал пре­ры­ваний от уст­рой­ства к хосту.

Зна­комь­тесь — libusb

Биб­лио­те­ка libusb пред­став­ля­ет со­бой наи­бо­лее универ­саль­ный ин­ст­ру­мент, ко­то­рый по­дой­дет как для Linux, так и для Windows (а так­же для FreeBSD и OS X). С по­мо­щью этой биб­лио­те­ки при­клад­ная про­грам­ма мо­жет ре­шать та­кие за­да­чи, как по­иск уст­рой­ства на шине USB и об­мен дан­ны­ми с ними.

Пре­ж­де чем при­сту­пать к ра­бо­те, убе­ди­тесь, что в ва­шей систе­ме уста­нов­ле­на биб­лио­те­ка libusb вер­сии не ниже 1.0 (этот со­вет от­но­сит­ся к Linux и FreeBSD).

Libusb под Windows

Биб­лио­те­ка libusb не вхо­дит в ваш ди­ст­ри­бу­тив Windows, так что при­дет­ся уста­но­вить ее са­мо­стоя­тель­но. До­маш­няя стра­ница про­ек­та libusb for Windows на­хо­дит­ся по ад­ре­су http://sourceforge.net/projects/libusb-win32/. Во­пре­ки на­званию, на­чи­ная с вер­сии 1.2, биб­лио­те­ка мо­жет ра­бо­тать и с 64‑бит­ной Windows. Я ре­ко­мен­дую вам восполь­зо­вать­ся са­мой по­следней вер­си­ей биб­лио­те­ки (на дан­ный мо­мент – 1.2.2.0). Ранние вер­сии libusb for Win32 со­дер­жа­ли ошиб­ки, из-за ко­то­рых биб­лио­те­ка мог­ла, на­при­мер, от­клю­чить все драй­ве­ры USB ра­зом (в том чис­ле – мы­ши и кла­виа­ту­ры). Не по­мо­га­ла да­же пе­ре­за­груз­ка Windows в безо­пас­ном ре­жи­ме. Осо­бен­но ве­се­ло все это вы­гля­дит на ком­пь­ю­те­ре, у ко­то­ро­го от­сут­ству­ют разъ­е­мы PS/2, так что мышь и кла­виа­ту­ру мож­но под­клю­чить толь­ко че­рез USB (кто-нибудь еще помнит мы­ши с под­клю­чением к по­сле­до­ва­тель­но­му пор­ту?). Кро­ме то­го, ранние вер­сии биб­лио­те­ки не уме­ли ра­бо­тать с уст­рой­ства­ми клас­са HID, ка­ко­вы­ми мы сей­час и занима­ем­ся.

По­ми­мо са­мой биб­лио­те­ки, в ди­ст­ри­бу­тив libusb вхо­дят ути­ли­ты для уста­нов­ки драй­ве­ров и ди­аг­но­сти­че­ская про­грам­ма, ко­то­рая по­зво­ля­ет про­ве­рить, «ви­дит» ли libusb ва­ше уст­рой­ства, а за­од­но – со­брать ин­фор­ма­цию об уст­рой­стве, ана­ло­гич­ную той, ко­то­рую вы­да­ет ути­ли­та lsusb.

Да бу­дет свет!

Те­перь, когда мы зна­ем, как уст­рой­ство взаи­мо­дей­ству­ет с ком­пь­ю­те­ром, мы мо­жем напи­сать ана­лог про­грам­мы Webmail Notifier для Linux. Но мы по­сту­пим луч­ше. Си­ла и мощь Linux за­клю­ча­ет­ся в том, что мно­гие по­лез­ные про­грам­мы вы­полнены в ви­де кон­соль­ных ути­лит, ко­то­ры­ми лег­ко управ­лять из команд­ной стро­ки и дру­гих про­грамм. Для на­ше­го уст­рой­ства мы на­пи­шем про­грам­му dclight, с по­мо­щью ко­то­рой мы смо­жем уста­нав­ли­вать про­из­воль­ное зна­чение яр­ко­сти для ка­ж­до­го све­то­дио­да. Вы­зов про­грам­мы вы­гля­дит как

dclight r g b

где r, g, b – зна­чения яр­ко­сти (от 0 до 255) для ка­ж­дой цве­то­вой со­став­ляю­щей. По­сле вы­зо­ва ути­ли­ты ко­ро­боч­ка Dream Cheeky бу­дет све­тить нам вы­бран­ным све­том до тех пор, по­ка мы не из­меним его зна­чение. Для вы­клю­чения уст­рой­ства нуж­но вы­звать

dclight 0 0 0

Как ви­дим, про­грам­му dclight бу­дет со­всем нетруд­но вы­звать из дру­гих про­грамм, в том чис­ле напи­сан­ных на скрип­то­вых язы­ках. Бла­го­да­ря кросс-плат­фор­мен­но­сти libusb ее мож­но ском­пи­ли­ро­вать и под Windows (ес­ли вы поль­зуе­тесь MinGW, вно­сить из­менений в ис­ход­ные тек­сты во­об­ще не при­дет­ся).

 #define DEV_VID 0x1D34
 #define DEV_PID 0x0004
 #define DEV_CONFIG 1
 #define DEV_INTF 0
 #define EP_IN 0x81
 unsigned char COMMAND_1[8] = {0x1F,0x1E,0x92,0x7C,0xB8,0x1,0x14,0x03};
 unsigned char COMMAND_2[8] = {0x00,0x1E,0x92,0x7C,0xB8,0x1,0x14,0x04};
 unsigned char COMMAND_ON[8] = {0x00,0x00,0x00,0x00,0x0,0x1,0x14,0x05};
 int main(int argc, char * argv[])
 {
	 ...
	 libusb_init(NULL);
	 libusb_set_debug(NULL, 3);
	 handle = libusb_open_device_with_vid_pid(NULL, DEV_VID, DEV_PID);
	 ...
	 if (libusb_kernel_driver_active(handle,DEV_INTF))
		 libusb_detach_kernel_driver(handle,DEV_INTF);
	 if ((ret = libusb_set_configuration(handle, DEV_CONFIG)) < 0)
	 {
		 printf(“Ошиб­ка кон­фи­гу­ра­ции\n”);
		 ...
	 }
	 if (libusb_claim_interface(handle, DEV_INTF) < 0)
	 {
		 printf(“Ошиб­ка ин­тер­фей­са\n”);
		 ...
	 }
	 ret = libusb_control_transfer(handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x9, 0x200, 0, COMMAND_1, 8, 100);
	 libusb_interrupt_transfer(handle, EP_IN, buf, 8, &ret,100);
	 ret = libusb_control_transfer(handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x9, 0x200, 0, COMMAND_2, 8, 100);
	 libusb_interrupt_transfer(handle, EP_IN, buf, 8, &ret,100);
	 COMMAND_ON[0] = r;
	 COMMAND_ON[1] = g;
	 COMMAND_ON[2] = b;
	 ret = libusb_control_transfer(handle, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x9, 0x200, 0, COMMAND_ON, 8, 100);
	 buf[7] = 0;
	 libusb_interrupt_transfer(handle, EP_IN, buf, 8, &ret,100);
	 if (buf[7] != 1) {
		 printf(“Сбой в управ­ле­нии уст­рой­ст­вом\n”);
		 ...
	 }
	 libusb_attach_kernel_driver(handle, DEV_INTF);
	 libusb_close(handle);
	 libusb_exit(NULL);
	 return 0;
}


В на­ча­ле про­грам­мы мы объ­яв­ля­ем несколь­ко по­лез­ных кон­стант. Пре­ж­де все­го это зна­чения VID и PID уст­рой­ства, с ко­то­рым нам пред­сто­ит ра­бо­тать, а так­же но­ме­ра ин­тер­фей­са и кон­фи­гу­ра­ции, ко­то­рые оно под­дер­жи­ва­ет. По­следние два зна­чения мы мог­ли бы уз­нать про­грамм­но, и ниже мы рас­ска­жем, как это сде­лать, но сей­час мы уп­ро­стим се­бе жизнь и же­ст­ко за­шьем в про­грам­му дан­ные, по­лу­чен­ные с по­мо­щью ути­ли­ты lsusb. На прак­ти­ке это вполне до­пусти­мо, по­сколь­ку для лю­бо­го кон­крет­но­го уст­рой­ства на­бо­ры ин­тер­фей­сов и кон­фи­гу­ра­ций – ве­ли­чи­на по­сто­ян­ная. Про­грамм­ная реа­ли­за­ция по­иска этих зна­чений име­ет смысл в при­ло­жениях, пред­на­зна­чен­ных для ра­бо­ты с боль­ши­ми груп­па­ми раз­лич­ных уст­ройств, и в этом слу­чае вы долж­ны очень хо­ро­шо ве­дать, что вы тво­ри­те и за­чем. Кон­стан­та EP_In оп­ре­де­ля­ет но­мер точ­ки досту­па для оп­ро­са пре­ры­ваний. Мас­си­вы COMMAND_1, COMMAND_2 и COMMAND_ON со­дер­жат опи­сан­ные ранее по­сле­до­ва­тель­но­сти бай­тов, ко­то­рые необ­хо­ди­мо пе­ре­дать для инициа­ли­за­ции уст­рой­ства и для управ­ления све­то­дио­да­ми.

Функ­ция libusb_init() инициа­ли­зи­ру­ет биб­лио­те­ку. Пра­ви­ла хо­ро­ше­го то­на тре­бу­ют, что­бы в кон­це про­грам­мы мы также вы­зва­ли libusb_exit().

Пер­вый ар­гу­мент libusb_init(), ко­то­рый мы остав­ля­ем рав­ным NULL, оп­ре­де­ля­ет иден­ти­фи­ка­тор сес­сии (кон­текст) ра­бо­ты с биб­лио­те­кой. Ис­поль­зо­вание в про­грам­ме несколь­ких раз­ных кон­тек­стов по­зво­ля­ет соз­да­вать неза­ви­си­мые сес­сии при ра­бо­те с биб­лио­те­кой libusb (так что вы­зов, на­при­мер, libusb_exit() в од­ной сес­сии не по­влия­ет на ра­бо­ту в дру­гой). Струк­ту­ра дан­ных, опи­сы­ваю­щая кон­текст, инициа­ли­зи­ру­ет­ся функ­ци­ей libusb_init(). По­сколь­ку в на­шей про­грам­ме мы яв­но управ­ля­ем вы­зо­ва­ми всех функ­ций libusb, мы мо­жем обой­тись без кон­тек­стов. Ес­ли же вы пи­ше­те раз­де­ляе­мую биб­лио­те­ку, в ко­то­рой за­дей­ство­ва­на функ­цио­наль­ность libusb, ис­поль­зо­вать кон­тек­сты вам про­сто необ­хо­ди­мо, так как поль­зо­ва­тель ва­шей биб­лио­те­ки мо­жет ис­поль­зо­вать и дру­гие биб­лио­те­ки, ко­то­рые то­же ра­бо­та­ют с libusb.

Функ­ция libusb_set_debug() оп­ре­де­ля­ет, на­сколь­ко мно­го­слов­ной бу­дет про­грам­ма в слу­чае ошиб­ки. Мы уста­нав­ли­ва­ем мак­си­маль­ный уро­вень ин­фор­ма­тив­но­сти. По­ми­мо это­го, мно­гие функ­ции libusb воз­вра­ща­ют чис­лен­ный код за­вер­шения опе­ра­ции, по ко­то­ро­му мож­но вы­ло­вить ин­фор­ма­цию об ошиб­ке (со­от­вет­ствую­щие кон­стан­ты оп­ре­де­ле­ны в фай­ле libusb.h).

Функ­ция libusb_open_device_with_vid_pid() ищет на шине уст­рой­ство USB по за­дан­ным зна­чениям Vendor ID и Product ID и от­кры­ва­ет пер­вое най­ден­ное для ра­бо­ты. Пред­ста­ви­те­ли стар­ше­го ком­пь­ю­тер­но­го по­ко­ления пом­нят вре­ме­на, когда при уста­нов­ке но­во­го уст­рой­ства тре­бо­ва­лось ука­зы­вать пор­ты и пре­ры­вания (иногда на пла­те са­мо­го уст­рой­ства, с по­мо­щью пе­ре­мы­чек). В на­шу эпо­ху без­ал­ко­голь­но­го пи­ва поль­зо­ва­тель ждет, что про­грам­ма са­ма по­ка­жет ему спи­сок под­хо­дя­щих уст­ройств в его сис­те­ме (ниже мы рас­ска­жем, как это мож­но сде­лать в слу­чае USB). Ес­ли под­хо­дя­щее уст­рой­ство най­де­но, функ­ция libusb_open_device_with_vid_pid() воз­вра­ща­ет ука­за­тель на струк­ту­ру libusb_device_handle, ко­то­рую мы и ис­поль­зу­ем да­лее для всех об­ра­щений к уст­рой­ству. По окон­чании ра­бо­ты с уст­рой­ством этот ука­за­тель пе­ре­да­ет­ся функ­ции libusb_close(). Сто­ит от­ме­тить, что на са­мом де­ле функ­ции, от­кры­ваю­щие и за­кры­ваю­щие уст­рой­ства, на ра­бо­ту этих уст­ройств не влия­ют никак. Вы­пол­няе­мые ими опе­ра­ ции ка­са­ют­ся толь­ко на­строй­ки струк­тур дан­ных внут­ри са­мой биб­лио­те­ки libusb.

Диа­лог с ко­ро­боч­кой

На­ша сле­дую­щая за­да­ча – вы­брать кон­фи­гу­ра­цию и ин­тер­фейс уст­рой­ства, в ре­зуль­та­те че­го нам от­кро­ют­ся вол­шеб­ные вра­та – точ­ки досту­па, че­рез ко­то­рые возмо­жен об­мен дан­ны­ми с уст­рой­ством. Од­на­ко для на­ча­ла сто­ит про­ве­рить, не за­хва­ти­ла ли уже доступ жад­ная опе­ра­ци­он­ная систе­ма. Функ­ция libusb_kernel_driver_active() по­зво­ля­ет оп­ре­де­лить, досту­пен ли за­дан­ный ин­тер­фейс, а функ­ция libusb_detach_kernel_driver() от­це­п­ля­ет от него драй­вер опе­ра­ци­он­ной систе­мы. Сле­дуя пра­ви­лу «где что взял, по­ло­жи об­рат­но», в кон­це ра­бо­ты про­грам­мы мы вы­зы­ва­ем функ­цию libusb_attach_kernel_driver(). Те­перь мы мо­жем сме­ло за­хва­ты­вать кон­фи­гу­ра­цию и ин­тер­фейс (функ­ции libusb_set_configuration() и libusb_claim_interface() со­от­вет­ствен­но).

Для ка­ж­до­го из че­ты­рех ти­пов пе­ре­дач дан­ных про­то­ко­ла USB в биб­лио­те­ке libusb оп­ре­де­ле­ны спе­ци­аль­ные функ­ции. Так, libusb_control_transfer() пред­на­зна­че­на для пе­ре­да­чи управ­ляю­щих со­об­щений. Пер­вый па­ра­метр функ­ции – иден­ти­фи­ка­тор от­кры­то­го уст­рой­ства. Да­лее сле­ду­ет ком­би­на­ция фла­гов, оп­ре­де­ляю­щих па­ра­мет­ры пе­ре­да­чи. В на­шем слу­чае (пе­ре­да­ча спе­ци­аль­ных управ­ляю­щих со­об­щений от хоста к уст­рой­ству) ис­поль­зу­ют­ся сле­дую­щие фла­ги:

  • LIBUSB_REQUEST_TYPE_CLASS оз­на­ча­ет, что со­об­щение спе­ци­фич­но для клас­са уст­рой­ства.
  • LIBUSB_RECIPIENT_INTERFACE ука­зы­ва­ет, что по­лу­ча­те­лем до­полнитель­ных дан­ных яв­ля­ет­ся ин­тер­фейс уст­ройств.
  • LIBUSB_ENDPOINT_OUT оп­ре­де­ля­ет на­прав­ление пе­ре­да­чи до­полнитель­ных дан­ных (от хоста к уст­рой­ству). В треть­ем, чет­вер­том и пя­том па­ра­мет­рах пе­ре­да­ют­ся но­мер за­про­са, зна­чение за­про­са и зна­чение ин­дек­са. Шестой па­ра­метр функ­ции – ука­за­тель на мас­сив до­полнитель­ных дан­ных; да­лее сле­ду­ет дли­на мас­си­ва в бай­тах. По­следний па­ра­метр – вре­мя ожи­дания под­твер­ждения со­об­щения в мил­ли­се­кун­дах.

Функ­ция libusb_interrupt_transfer() пред­на­зна­че­на для пе­ре­да­чи пре­ры­ваний. Пер­вый па­ра­метр – иден­ти­фи­ка­тор уст­рой­ства. Да­лее сле­ду­ет но­мер точ­ки досту­па. В от­ли­чие от управ­ляю­щих со­об­щений, ко­то­рые пе­ре­да­ют­ся по стан­дарт­ной точ­ке досту­па, но­мер ко­то­рой мож­но не ука­зы­вать, для пе­ре­да­чи пре­ры­ваний этот но­мер необ­хо­ди­мо ука­зать яв­но. За­то все осталь­ные па­ра­мет­ры (на­прав­ление пе­ре­да­чи дан­ных и т. п.) функ­ция оп­ре­де­ля­ет са­ма, по опи­санию ука­зан­ной точ­ки досту­па. Тре­тий па­ра­метр функ­ции – ад­рес мас­си­ва, ко­то­рый ис­поль­зу­ет­ся для пе­ре­да­чи или прие­ма дан­ных. Дли­на мас­си­ва пе­ре­да­ет­ся в чет­вер­том па­ра­мет­ре. Пя­тый па­ра­метр – это ука­за­тель на пе­ре­мен­ную, в ко­то­рой функ­ция воз­вра­ща­ет ко­ли­че­ство фак­ти­че­ски пе­ре­дан­ных байт дан­ных. По­следний па­ра­метр – вре­мя ожи­дания в мил­ли­се­кун­дах.

Функ­ция libusb_bulk_transfer() пред­на­зна­че­на для пе­ре­да­чи боль­ших мас­си­вов дан­ных. За­го­ло­вок этой функ­ции вы­гля­дит так же, как и у функ­ции libusb_interrupt_transfer().

Те­перь вы, конеч­но, за­хо­-ти­те уз­нать, ка­кая функ­ция вы­пол­ня­ет изо­хрон­ную пе­ре­да­чу дан­ных. Я мог бы ска­зать вам, но не ста­ну. Де­ло в том, что рас­смот­рен­ный на­ми блок функ­ций пред­на­зна­чен для ра­бо­ты с уст­рой­ством в бло­ки­рую­щем ре­жи­ме (за­прос – ожи­дание от­ве­та – от­вет), ко­то­рый яв­ля­ет­ся са­мым про­стым. У биб­лио­те­ки libusb есть и дру­гой, небло­ки­рую­щий (асин­хрон­ный) ре­жим, в ко­то­ром функ­ции пе­ре­да­чи дан­ных не приоста­нав­ли­ва­ют ра­бо­ту вы­зы­ваю­щей про­грам­мы. В этом ре­жи­ме реа­ли­зо­ва­ны все че­ты­ре ти­па пе­ре­да­чи дан­ных, в том чис­ле и изо­хрон­ный. Для бло­ки­рую­ще­го же ре­жи­ма функ­ции изо­хрон­ной пе­ре­да­чи дан­ных про­сто нет, и в об­щем нетруд­но по­нять, по­че­му.

Про­грам­ма ком­пи­ли­ру­ет­ся стро­кой

gcc dclight.c -o dclight -lusb-1.0

Для по­лу­чения досту­па к уст­рой­ству USB про­грам­ма долж­на об­ла­дать пра­ва­ми root (или же вы долж­ны вклю­чить сво­его поль­зо­ва­те­ля в груп­пу, имею­щую пра­во запи­си в usbfs – как это сде­лать, ищи­те в до­ку­мен­та­ции к сво­ему ди­ст­ри­бу­ти­ву). Что­бы ути­ли­ту dclight мож­но бы­ло вы­зы­вать из обыч­ных про­грамм, сде­ла­ем root ее вла­дель­цем и уста­но­вим для нее «лип­кий бит»:

# chown root dclight
# chmod a+s dclight

Те­перь мы мо­жем кон­тро­ли­ро­вать поч­то­вый ин­ди­ка­тор из Linux (и Windows) и за­ста­вить его де­лать то, что нуж­но нам!

Об­ра­ти­те внимание, что мы вы­пол­ня­ем про­це­ду­ру инициа­ли­за­ции уст­рой­ства при ка­ж­дом вы­зо­ве про­грам­мы dclight, хо­тя это доста­точ­но сде­лать один раз, при под­клю­чении уст­рой­ства к компь­ю­те­ру. На­ша про­грам­ма бы­ла бы за­мет­но сложнее, ес­ли бы нам тре­бо­ва­лось учи­ты­вать, бы­ло ли уст­рой­ство уже инициа­ли­зи­ро­ва­но ранее, но ниче­го та­ко­го не тре­бу­ет­ся. По­сле то­го как мы вы­полнили инициа­ли­за­цию уст­рой­ства, оно по­про­сту иг­но­ри­ру­ет по­сле­дую­щие коман­ды инициа­ли­за­ции. Как уже от­ме­ча­лось вы­ше, уст­рой­ство не вы­клю­ча­ет­ся ав­то­ма­ти­че­ски при за­вер­шении ра­бо­ты с libusb (да биб­лио­те­ка и не зна­ет, как его вы­клю­чить). Для нас это оз­на­ча­ет, что све­то­дио­ды бу­дут го­реть с за­дан­ной яр­ко­стью и по­сле за­вер­шения ра­бо­ты про­грам­мы. Та­кое по­ве­дение со­от­вет­ству­ет на­ше­му за­мыс­лу, но, во­об­ще го­во­ря, оно вы­гля­дит несколь­ко непри­выч­но для при­клад­ных про­грам­ми­стов, ко­то­рые при­вык­ли, что, за­вер­шая ра­бо­ту, про­грам­ма при­би­ра­ет за со­бой… Доб­ро по­жа­ло­вать в мир ра­бо­ты с обо­ру­до­ванием на­пря­мую! Здесь все в ва­ших ру­ках.

Мы оста­ви­ли в сто­роне один важ­ный во­прос: вре­мен­ные за­держ­ки. При управ­лении уст­рой­ством USB нам мо­жет по­на­до­бить­ся де­лать ме­ж­ду коман­да­ми оп­ре­де­лен­ные пау­зы. В слу­чае WebMail Notifier это­го не по­тре­бо­ва­лось, по­сколь­ку ин­тер­фейс уст­рой­ства сам соз­да­ет необ­хо­ди­мые пау­зы (об­мен со­об­щения­ми для уста­нов­ки оп­ре­де­лен­но­го зна­чения яр­ко­сти све­то­дио­дов со все­ми за­держ­ка­ми под­твер­ждаю­щих со­об­щений со сто­ро­ны уст­рой­ства занима­ет око­ло 0,01 се­кун­ды). В об­щем же слу­чае нам мо­гут по­на­до­бить­ся спе­ци­аль­ные тай­ме­ры.

Ум­ный по­иск

Функ­ция libusb_open_device_with_vid_pid(), ко­то­рую мы ис­поль-­зо­ва­ли вы­ше, реа­ли­зу­ет «бы­ст­рый и гряз­ный» спо­соб по­иска и инициа­ли­за­ции уст­рой­ства по за­дан­ным зна­чениям VID и PID. Она удоб­на в от­ла­доч­ных про­грам­мах, но в при­ло­жениях для серь­ез­но­го при­менения ее луч­ше не ис­поль­зо­вать. Недостат­ки libusb_open_device_with_vid_pid() оче­вид­ны: эта функ­ция не по­з­­во­ля­ет инициа­ли­зи­ро­вать несколь­ко уст­ройств с оди­на­ко­вы­ми VID и PID и со­вер­шен­но не го­дит­ся для тех слу­ча­ев, когда уст­рой­ство вы­би­ра­ет­ся не по па­ре VID и PID, а, на­при­мер, по клас­су. По­иск уст­ройств по всем па­ра­мет­рам мож­но вы­полнить с по­мо­щью функ­ций libusb_get_device_list() и libusb_get_device_descriptor().

Функ­ция libusb_get_device_list() по­зво­ля­ет по­лу­чить спи­сок всех уст­ройств USB, об­на­ру­жен­ных в систе­ме. Она соз­да­ет мас­сив ука­за­те­лей на струк­ту­ры libusb_device, ка­ж­дая из ко­то­рых со­от­вет­ству­ет од­но­му эк­зем­п­ля­ру уст­рой­ства USB.

Функ­ция libusb_get_device_descriptor() по­зво­ля­ет по­лу­чить опи­сание уст­рой­ства, пред­став­лен­но­го струк­ту­рой libusb_device, в ви­де струк­ту­ры libusb_device_descriptor. Эта струк­ту­ра со­дер­жит VID и PID уст­рой­ства, ко­ды клас­са и под­клас­са, чи­та­бель­ные име­на про­из­во­ди-­те­ля и са­мо­го уст­рой­ства, се­рий­ный но­мер и чис­ло кон­фи­гу­ра­ций уст­рой­ства. Этой ин­фор­ма­ции обыч­но доста­точ­но для то­го, что­бы вы­брать уст­рой­ство для под­клю­чения. Вы­бран­ное уст­рой­ство от­кры­ва­ет­ся с по­мо­щью функ­ции libusb_open(), ко­то­рой пе­ре­да­ет­ся ука­за­тель на струк­ту­ру libusb_device.

Ниже при­во­дит­ся фраг­мент про­грам­мы, в ко­то­ром вы­бор уст­ройств вы­пол­ня­ет­ся опи­сан­ным спо­со­бом. Уст­рой­ство вы­би­ра­ет­ся по зна­чению VID из струк­ту­ры libusb_device_descriptor, но его точ­но так же мож­но вы­би­рать и по зна­чениям дру­гих по­лей струк­ту­ры.

	 libusb_device ** list;
	 libusb_device * found = NULL;
	 ssize_t count;
	 ssize_t i = 0;
	 int err = 0;
	 libusb_context * ctx;
	 libusb_init(&ctx);
	 if ((count = libusb_get_device_list(ctx, &list)) < 0) {
			 printf(“Не­воз­мож­ная ошиб­ка, но, тем не ме­нее...\n”);
			 return -1;
		 }
	 for (i = 0; i < count; i++) {
		 libusb_device * device = list[i];
		 struct libusb_device_descriptor desc;
		 libusb_get_device_descriptor(device, &desc);
		 if (desc.idVendor == DEV_VID) {
			 found = device;
			 break;
 
		 }
	 }
	 if (found) {
		 err = libusb_open(found, &handle);
		 if (err) {
			 printf(“Не­воз­мож­но от­крыть уст­рой­ст­во\n”);
			 return -1;
		 }
	 } else {
		 printf(“Уст­рой­ст­во не най­де­но\n”);
		 return -1;
	 }
 …
 libusb_free_device_list(list, 1);

Лож­ка дег­тя

По­сле пе­ре­чис­ления всех досто­инств libusb нель­зя не упо­мя­нуть об од­ном недостат­ке. На дан­ный мо­мент биб­лио­те­ка иг­но­ри­ру­ет тот факт, что уст­рой­ства USB мо­гут под­клю­чать­ся и от­клю­чать­ся в про­цес­се нор­маль­ной ра­бо­ты систе­мы. Ес­ли уст­рой­ство, с ко­то­рым мы ра­бо­та­ем, бы­ло от­клю­че­но во вре­мя ра­бо­ты про­грам­мы, сле­дую­щее об­ра­щение к уст­рой­ству вернет од­но из воз­мож­ных со­об­щений об ошиб­ке, из ко­то­рых мож­но сде­лать вы­вод, что уст­рой­ство от­клю­че­но. Ес­ли вы хо­ти­те, что­бы ва­ша про­грам­ма реа­ги­ро­ва­ла на под­клю­чение но­вых уст­ройств, вам сле­ду­ет пе­рио­ди­че­ски вы­пол­нять по­иск уст­ройств на шине USB, как это бы­ло опи­са­но вы­ше. На дан­ный мо­мент это все, что мож­но сде­лать для об­на­ру­жения ди­на­ми­че­ских под­клю­чений и от­клю­чений в рам­ках libusb – то есть, не при­бе­гая к спе­ци­аль­ным сред­ствам ОС. Раз­ра­бот­чи­ки libusb пред­ла­га­ют до­ба­вить в биб­лио­те­ку функ­ции об­рат­но­го вы­зо­ва, опо­ве­щаю­щие про­грам­му об из­менении спи­ска доступ­ных уст­ройств, но на мо­мент напи­сания ста­тьи эта функ­цио­наль­ность не реа­ли­зо­ва­на.

Те­перь вы и са­ми мо­же­те ос­ча­ст­ли­вить Linux-со­об­ще­ст­во, до­ба­вив в сис­те­му под­держ­ку но­во­го уст­рой­ст­ва USB (и я про­сто уве­рен, что вам не тер­пит­ся это сде­лать). Мы же про­дол­жим зна­ком­ст­во со сред­ст­ва­ми до­маш­ней ав­то­ма­ти­за­ции, управ­ляе­мы­ми с по­мо­щью USB.

Персональные инструменты
купить
подписаться
Яндекс.Метрика