Лабораторная работа №5 Программирование интерфейса USB Цель работы: Получение навыка работы с шиной USB с использованием универсального кроссплатформенного интерфейса libusb. План: 1. Определить вариант лабораторной работы в соответствии с ФИО студента. 2. Выполнить инициализацию библиотеки libusb 3. Выполнить поиск необходимого устройства в списке подключенных устройств и открыть найденное устройство USB 4. Считать данные дескрипторов по варианту 5. Отсоединить устройство от ядра ОС 6. Захватить интерфейс устройства 7. Выполнить взаимодействия с устройством через структуру доступа к устройству (libusb_device_handle) по варианту 8. Отпустить интерфейс устройства 9. Присоединить устройство к ядру ОС 10. Выполнить деинициализацию библиотеки libusb 1. Определить вариант лабораторной работы в соответствии с ФИО студента. Считать данные из дескрипторов USB устройства |Ф| % 5: 0 - Идентификатор производителя и продукта VID:PID (idVendor, idProduct), из дескриптора устройства, вывести в HEX 1 - Определить максимальный уровень потребления устройства устройства из дескриптора конфигурации, вывести в миллиамперах 2 - Количество конечных точек интерфейса и протокол интерфейса (bNumEndpoints, bInterfaceProtocol) из дескриптора интерфейса 3 - Информацию о конечных точках устройства: адреса, тип передачи, максимальный размер пакета (bEndpointAddress, bmAttributes, wMaxPacketSize), вывести адрес в HEX, тип передачи: Interrupt, Bulk, Control или Isochronous, максимальный размер пакета в байтах 4 - Вывести строковые значения изготовителя, продукта и серийного номера из строковых дескрипторов и вывести на экран. Номера строковых дескрипторов располагаются в дескрипторе устройства в полях iManufacturer, iProduct, iSerial соответственно. Если номер строкового дескриптора == 0, то вывести "none" без запроса строкового дескриптора Взаимодействие c USB устройством: |О| % 6: 0 - Считывать относительное перемещение USB мыши, рассчитывать абсолютные координаты и выводить их на экран. Завершить работу с устройством по нажатию любой кнопки USB мыши; 1 - Считывать нажатие средней (колесо), левой и правой кнопок мыши и выводить на экран факты нажатия. Завершить работу с устройством по кручению колеса мыши; 2 - Считывать относительное перемещение колеса мыши, рассчитывать абсолютное перемещение и выводить его на экран, до тех пор, пока не нажата правая кнопка мыши; 3 - Считывать коды нажатия на USB клавиатуре, завершить считывание на нажатию клавиши Pause Break (код 0x48); 4 - Включать и выключать светодиоды NumLock,CapsLock,ScrollLock на USB клавиатуре, до тех пор, пока не нажата любая клавиша. 5 - Считать первый сектор (первые 512 байт) произвольной USB флешки и вывести на экран в HEX формате Для работы с библиотекой libusb необходимо: - подключить заголовочный файл библиотеки #include - выполнять компиляцию с ключем: -lusb-1.0 2. Выполнить инициализацию библиотеки libusb Инициализация выполняется функцией libusb_init. int libusb_init (libusb_context ** context) Вместо context передать в функцию NULL. Функция возвратит 0 при успешной инициализации, иначе код ошибки. 3. Выполнить поиск необходимого устройства в списке подключённых устройств Поиск USB устройства выполняется по следующему алгоритму: 3.1 Определить номер изготовителя и модели (VID:PID) устройства, с которым предстоит работа; В терминале выполнить команду lsusb и в поле ID определить номер изготовителя и модели в формате VID:PID Пример: lsusb Bus 001 Device 004: ID 090c:1000 Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.) Flash Drive Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. LAN9500 Ethernet 10/100 Adapter / SMSC9512/9514 Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Пример получения полного описания устройства с указанным VID:PID lsusb -d 0424:ec00 -v 3.2 В программе запросить список устройст, подключенных к системе (libusb_device** list;) Список устройств храниться в виде массива ссылок на ссылки к структуре устройсва (libusb_device). Для получения используется функция: int count = libusb_get_device_list(NULL, &list); Данная функция заполняет определённый выше список list подключёнными устройствами и возвращает количество устройств в массиве. Таким образом, доступ к i-ой ссылке на структуру устройства осуществляется следующим образом: libusb_device* dev = list[i]; Пример: libusb_device** list; int count = libusb_get_device_list(NULL, &list); 3.3 Для каждого устройства из списка запросить дескриптор устройства (struct libusb_device_descriptor) Запрос дескриптора устройства (struct libusb_device_descriptor) осуществляется по структуре устройства при помощи следующей функции: int libusb_get_device_descriptor(libusb_device *dev,struct libusb_device_descriptor *dd); В результате структура дескриптора dd будет заполнена дескриптором устройства dev. Функция возвратит 0 в случае успеха или номер ошибки в ином случае. Пример: struct libusb_device_descriptor deviceDesk; err = libusb_get_device_descriptor(list[i],&deviceDesk); if(err != 0){ printf("Ошибка получения дескриптора устройства\n"); exit(1); } 3.4 Сравнить значения номера изготовителя и модели (поля idVendor, idProduct) с искомыми, и если они совпадают, то прекратить поиск, иначе перейти к следующему устройству в списке; Оценить совпадение полей idVendor и idProduct с искомыми VID и PID соответственно. 3.5 Открыть найденное USB устройство и получить структуру доступа к USB устройству ( libusb_device_handle ) Получение структуры доступа к устройству осуществляется функцией: int libusb_open(libusb_device *dev, libusb_device_handle **dev_handle) В функцию передается USB устройство dev, которое нужно открыть. Функция открывает утройство и заполняет структуру доступа к устройству, указанную в dev_handle. При успешном открытии, функция возврящает 0, иначе - код ошибки. Пример: libusb_device_handle *dev_handle; err = libusb_open(findedDev, &dev_handle); if(err != 0){ printf("Ошибка открытия устройства. Код: %i\n", err); exit(1); } 3.6 Очистить запрошенный список USB устройств. Очистка списка осуществляется для освобождения памяти, выделенной под список устройств. Освобождение осуществляется при помощи функции: void libusb_free_device_list(libusb_device ** list, int unref_devices); В функцию передаётся очищаемый список устройств list и номер структуры устройства, который очищать не следует (например это найденное устройство и оно потребуется в будущем). В нашем случае устройство уже открыто и структура не понадобится, следовательно unref_devices = 0. 4. Считать данные дескрипторов по варианту Дескрипторы из устройства запрашиваются через структуру устройства (libusb_device), а не структуру доступа (libusb_device_handle). Для получания структуры устройства через структуру доступа используется следующая функция: libusb_device * libusb_get_device (libusb_device_handle *dev_handle) Следующие структуры описывают различные виды дескрипторов: struct libusb_device_descriptor - дескриптор устройства; struct libusb_config_descriptor - дескриптор конфигурации; struct libusb_interface_descriptor - дескриптор интерфейса; struct libusb_endpoint_descriptor - дескриптор конечной точки. Функция определения дескриптора устройства: int libusb_get_device_descriptor (libusb_device *dev, struct libusb_device_descriptor *desc) Получает дескриптор устройства dev, и заполняет структуру по ссылке desc. Возвращает 0, в случае успешного чтения, иначе - код ошибки. Функция определения активного дескриптора конфигурации: int libusb_get_active_config_descriptor (libusb_device *dev, struct libusb_config_descriptor **config) Получает дескриптор активной конфигурации устройства dev, выделяет память под этот дескриптор и заполняет ссылку на дескриптор конфигурации config. Возвращает 0, в случае успешного чтения, иначе - код ошибки. Дескриптора конфигурации требует очистки после использования при помощи следующей функции: void libusb_free_config_descriptor (struct libusb_config_descriptor *config) Дескрипторы интерфейсов и дескрипторы конечных точек содержатся в дескрипторе конфигурации в виде полей-массивов следующей структуры libusb_config_descriptor ├ ->bNumInterfaces (тип uint8_t) └ ->interface[] (тип struct libusb_interface*) ├ ->num_altsetting (тип int) └ ->altsetting[] (тип struct libusb_interface_descriptor*) ├ ->bNumEndpoints (тип uint8_t) └ ->endpoint[] (тип struct libusb_endpoint_descriptor*) Пример перебора всех интерфейсов, альтернативных интерфейсов и конечных точек: struct libusb_config_descriptor* configDsc; err = libusb_get_active_config_descriptor(dev, &configDsc); if(err != 0){ printf("Ошибка считывания дескриптора конфикурации"); exit(1);} for(int i=0; i < configDsc->bNumInterfaces; i++){ struct libusb_interface interf = configDsc->interface[i]; for(int ialt=0; ialt < interf.num_altsetting; ialt++){ struct libusb_interface_descriptor interfDesc = interf.altsetting[ialt]; for(int iep=0; iep < interfDesc.bNumEndpoints; iep++){ struct libusb_endpoint_descriptor epDesc = interfDesc.endpoint[iep]; printf("interface: %i alter_interface: %i EP: %i\n",i,ialt,iep); } } } Получение строковых дескрипторов по номеру: int libusb_get_string_descriptor_ascii (libusb_device_handle *dev_handle, uint8_t desc_index, unsigned char *data, int length) desc_index - номер строкового дескриптора data - адрес записи строкового значения length - длина буфера для записи 0 - успех, иначе - ошибка 5. Отсоединить устройство от ядра ОС По умолчанию ядро операционной системы захватывает управление USB устройством. Перед взаимодействием с устройством его необходимо отсоединить от ядра. Отсоединение от ядра выполняется следующим образом: 5.1 Проверка захвата USB устройства ядром ОС Проверка захвата выполняется при помощи функции: int libusb_kernel_driver_active(libusb_device_handle *dev_handle, int interface_number) dev_handle - структура доступа к USB устройству interface_number - номер отсоединяемого интерфейса USB устройства Функция вернет 0 - если устройство не захвачено ядром, 1 - если устройство захвачено ядром и код ошибки в ином случае. 5.2 Отсоединить устройство от ядра, если оно оказалось захваченным Отсоединение устройства от ядра осуществляется следующей функцией: int libusb_detach_kernel_driver(libusb_device_handle * dev_handle, int interface_number) dev_handle - структура доступа к отсоединяемому USB устройству interface_number - номер отсоединяемого интерфейса USB устройства Функция вернет 0 в случае успешного отсоединения, иначе - код ошибки. 6. Захватить интерфейс устройства Прежде чем выполнять взаимодействия с устройством через один из его интерфейсов, необходимо выполнить захват интерфеса устройства. Захват выполняется для того, чтобы пердотвратить одновременный множественный доступ к устройству. Захват интерфейса устройства выполняется при помощи следующей функции: int libusb_claim_interface(libusb_device_handle *dev_handle, int interface_number) dev_handle - структура доступа к USB устройству interface_number - номер захватываемого интерфейса USB устройства Функция вернет 0 в случае успешного захвата интерфейса, иначе - код ошибки. 7. Выполнить взаимодействия с устройством через структуру доступа к устройству (libusb_device_handle) по варианту Взаимодействие сс устройствами выполняется при помощи одного из 4-х методов передачи данных по USB: - Control - специальные данные, управление работой протокола, конфигурирование устройства - Interrupt - событийная передача данных по прерыванию (на деле - заданный период опроса) - Bulk - произвольные блоки данных - Isochronous - использования части полосы шины для передачи данных с гарантируемым временем ожидания без гарантии доставки Для различных типов устройст использются различные методы передачи. Для устройств в рамках вариатов к лаборатторной работе: - Interrupt для считывания относительного перемещения, кнопок и колеса мыши; - Interrupt для считывания нажатий на клавиатуре; - Control для управления светодиодами NumLock,CapsLock,ScrollLock; - Bulk для считывания секторов USB флешки. После определения метода передачи данных, необходимо определится, через какие конечные точки выполнять передачу данных. Конечные точки устройства всегда однонаправленны, из чего следует, что с входящих точек можно только принимать информацим, а в исходящие - только отправлять. Для определения всех конечных точек устройства необходимо в терминале выполнить команду: lsusb -d VID:PID -v где вместо VID:PID указать номер производителя и номер изделия исследуемого устройства. Например: lsusb -d 0424:ec00 -v В результате отобразятся все дескрипторы USB устройства, в том числе дескрипторы конечных точек. Каждый дескриптор конечной точки описывает одну конечную точку. При этом в поле bEndpointAddress указывает HEX адрес конечной точки и краткое описание. Исходящие конечные точки имеют адреса 0x00-0x7f, входящие 0x80-0xff. Помимо этого в Transfer Type можно обнаружить метод передачи конечной точки. По указаной информации и персональному варианту лабораторной работы необходимо определить через какую конечную точку вести передачу данных и какой у нее адрес. Для каждого вида передачи USB в библиотеке существует соответствующая функция, используемая как для передачи данных, так и для приёма. Решение о передаче или приёме данных принимается исходя из адреса конечной точки, через которую будет выполняться информационный обмен. Если адрес конечной точки исходящий (0x00-0x7f), то будет выполняться передача, в ином случае - прием. Функции для передачи/приёма данных через USB: int libusb_interrupt_transfer(struct libusb_device_handle *dev_handle, unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout) int libusb_bulk_transfer(struct libusb_device_handle *dev_handle, unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout) где endpoint - адрес конечной точки, используемой для передачи/приема данных data - адрес данных. В случае приема - адрес для записи принимаемых данных, в случае передачи - адрес отправляемых данных. length - В случае приема - размер приемного буфера (максимальное количество данных, которое можно принять указаный в data буфер), в случае передачи - количество байт, которые нужно отправить. transferred - ссылка на целочисленную переменную, в которую будет записано количество переданных/принятых байт. timeout - максимальное время ожидания выполнения транзакции в миллисекундах. Если за это время транзакция не выполнена, функция вернет код ошибки по таймауту. Функции возввращают 0 в случае успешного приёма/передачи, иначе - код ошибки. Для Control приема/передачи. Так как формат передачи типа Control строго регламентирован, то для него используется собственная функция приёма/передачи данных: int libusb_control_transfer(libusb_device_handle *dev_handle, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) где поля соответствуют формату стандартных запросов USB. data - адрес данных. В случае приема - адрес для записи принимаемых данных, в случае передачи - адрес отправляемых данных. timeout - максимальное время ожидания выполнения транзакции в миллисекундах. Если за это время транзакция не выполнена, функция вернет код ошибки по таймауту. Функции возввращают 0 в случае успешного приёма/передачи, иначе - код ошибки. Для задач по вариатам лабораторной: Для считывания информации с USB мыши необходимо считать данные при помощи метода Interrupt с 0-м таймаутом. Считываение завешриться как только с мыши поступит какое-либо событие. В приемном буфере окажется 4 байта от USB устройства следующего формата: Байт 0 (битовая маска): Y_overflow X_overflow Y_sign_bit X_sign_bit Always_1 Middle_Btn Right_Btn Left_Btn Байт 1: X_movement Байт 2: Y_movement Байт 3: Mostly_0 Mostly_0 5th_btn_down 4th_btn_down Z3 Z2 Z1 Z0 Z - 4-х битное знаковое число, определяющее относительное перемещение колеса мыши. Для перевода относительного перемещения в знакковый int: int dWheel = data[3] & 0xf; dWheel = dWheel > 7 ? dWheel - 0x10 : dWheel; Для считывания нажатий на клавиатуре необходимо считать данные при помощи метода Interrupt с 0-м таймаутом. Считываение завешриться как только с клавиатуры поступит событие. В приемном буфере окажется 8 байт, при этом 3-й байт - код нажатия клавиши. Для считывание сектора с USB флешки необходимо сначало послать USB SCSI запрос методом Bulk на чтение сектора данных в исходящую конечную точку интерфейса USB флешки, а затем cчитать USB SCSI ответ с входящей конечной точки, содержащий данные запрошенного сектора. USB SCSI запрос 1-го сектора: uint8_t req[] = { 0x55, 0x53, 0x42, 0x43, //ASCII "USBC" 0x01, 0x00, 0x00, 0x00, //Tag 0x00, 0x40, 0x00, 0x00, //DataTransferLength 0x80, //Flags 0x00, 0xaa, //Flags 2 //SCSI 0x28, //Read(0x28) 0x00, //Flags 0x00, 0x00, 0x00, 0x00, 0x00, //LBA(Address) 0x00, 0x20, //Transfer length 0x00, //Control 0x00,0x00,0x00,0x00,0x00,0x00 //align }; USB SCSI ответ будет содержать 512 байт 1-го сектора USB флеш. Для вывода буфера используйте функцию вывода буфера в HEX, описанной в приложении. Для управления светодиодами USB клавиатуры используется Control передача 3-х битной маски состояния светодиодов (одного байта) на конечную точку с адресом 0x00. Флаги маски: 1-NumLock 2-Caps 4-Scroll Параметры стандартного запроса USB для управления светодиодами: bmRequestType = 0x21 bRequest = 0x9 wValue = 0x0200 wIndex = 0 uint16_t wLength = 1 8. Отпустить интерфейс устройства После завершение работы с интерфейсом, его необходимо освободить. Освобождение интерфейса выполняется при помощи функции: int libusb_release_interface (libusb_device_handle *dev_handle, int interface_number) где interface_number - номер освобождаемого интерфейса. Функции возввращают 0 в случае успешного приёма/передачи, иначе - код ошибки. 9. Присоединить устройство к ядру ОС После завершения работы с устройством, управление его интерфесом необходимо передать ядру ОС. Для передачи управления интерфейса ядру ОС используется функция: int libusb_attach_kernel_driver (libusb_device_handle *dev_handle, int interface_number) где interface_number - номер интерфейса. 10. Закрыть USB устройство void libusb_close (libusb_device_handle *dev_handle) 11. Выполнить деинициализацию библиотеки libusb Функция libusb_exit(NULL); Приложение: Функция вывода буфера в HEX void hexDump (void *addr, int len) { int i; unsigned char buff[17]; unsigned char *pc = (unsigned char*)addr; if (len == 0) { printf(" ZERO LENGTH\n"); return; } if (len < 0) { printf(" NEGATIVE LENGTH: %i\n",len); return; } for (i = 0; i < len; i++) { if ((i % 16) == 0) { if (i != 0) printf (" %s\n", buff); printf (" %04x ", i); } printf (" %02x", pc[i]); if ((pc[i] < 0x20) || (pc[i] > 0x7e)) buff[i % 16] = '.'; else buff[i % 16] = pc[i]; buff[(i % 16) + 1] = '\0'; } while ((i % 16) != 0) { printf (" "); i++; } printf (" %s\n", buff); } Полная справочная информация по libusb доступна по адресу: http://libusb.sourceforge.net/api-1.0/modules.html