Как стать автором
Обновить

Написание userspace драйвера мыши для консоли Linux

На предмете «Системное программное обеспечение» мне дали задание на курсовую написать драйвер мыши для консоли Linux. Этот драйвер должен получать информацию о подключенной USB-мышке, отключать модуль ядра, через который она работает, получать перемещения мыши и соответственно рисовать в консоли курсор. Итак, вот что из этого получилось.

Рассмотрев варианты, я избрал userspace драйвер, поскольку его легче написать с помощью стороннего API, чем полностью писать модуль ядра. Как API я использовал libusb-0.1 (libusb), так как документации по этой версии больше, чем по libusb-1.0. Да и методом проб и ошибок оказалось, что мой openSUSE 11.4 использует версию 1.0 и библиотеку совместимости для работы с libusb-0.1, но не хочет подключать хедеры ни 1.0, ни 0.1, так что скомпилировать драйвер невозможно, но уже скомпилированный работает.

Поэтому для компиляции пришлось использовать live-cd ubuntu 9.04 с установленными libusb-0.1 и gcc. Программа написана на С, запускается с параметрами . Первые два параметра отвечают за выбор устройства, номера надо получить заранее с помощью команды lsusb. Третий параметр необязателен, он отвечает за количество байт, которые будем получать с устройства (по умолчанию 4 байта).

В результате мы получаем всю информацию, которую можно получить о мышке с помощью libusb.

Итак, собственно сам код:
#include <stdio.h>
#include <string.h>
#include <usb.h>
#include <usbpp.h>

#define DEBUG_LEVEL 0

struct usb_bus *busses,*bus,*dbus;

int main(int argc, char *argv[])
{
        struct usb_device *dev;
        char *buf;
        int n,x,r, ret;
        char *ibus, *idev;
        char string[64];
        int found = 0;       
        usb_dev_handle *udev;
        
        usb_set_debug(DEBUG_LEVEL);

        usb_init(); //initilize the usb library
        usb_find_busses();
        usb_find_devices();
        busses=usb_get_busses();

        //use the command line arguments for Bus and Device
        if (argc >= 3) {
                ibus = argv[1];
                idev = argv[2];
        }
        else { printf("Usage: usbtest <Bus> <Device> [<numBytes>])\n"); exit(1); }
        
        printf("Bus = %s Device = %s\n", ibus, idev);

        for (bus=busses; bus && (found == 0); bus=bus->next) { // busses loop
                if (strcmp(bus->dirname, ibus) == 0) {
                        for (dev=bus->devices; dev; dev=dev->next) { // devices loop
                                if (strcmp(dev->filename, idev) == 0) { dbus=bus; found=1; break; }
                        }
                }
        }
        
        if (found == 0) { printf("Unable to find the required device !\nexiting\n"); exit(1); }
        
        printf("Found device\n");
        printf("Now we are dealing with device from vendor ID : %d (%x) \n",dev->descriptor.idVendor,dev->descriptor.idVendor);
        printf("Trying to open the device...\n");
        
        if (udev=usb_open(dev)) printf("Device opened successfully.\n");
        else { printf("Operation failed :-("); exit(1);}

        buf=(char*)calloc(1,100);
        if (usb_get_driver_np(udev,0,buf,100)) printf("Could not read the driver name :-( %s\n",buf); 
        else printf("Kernel Using Driver :\n");

        // detach the driver from the kernel , seems to be just like rmmod? 
        if (usb_detach_kernel_driver_np(udev,0)) printf("Error detaching the device :-(\n"); 
        else printf("Device detached successfully from the kernel.\n");

        // reserving the device interface for our application, if another driver/software
        //is using the device , it will return 'interface busy'
        if (r=usb_claim_interface(udev,0)) printf("Interface Claimed !!\n"); 
        printf("Interface Claim Status : %d\n",r);

        printf("Device Protocol : %d\n",dev->descriptor.bDeviceProtocol);
        printf("Report Length : %d\n",dev->descriptor.bLength);
        printf("Decriptor Type : %d\n",dev->descriptor.bDescriptorType);
        printf("End Points : %d\n",dev->config->interface->altsetting->bNumEndpoints);
        printf("Interface Class : %d\n",dev->config->interface->altsetting->bInterfaceClass);
        printf("Protocol : %d\n",dev->config->interface->altsetting->bInterfaceProtocol);
        printf("Interface Number: %d\n",dev->config->interface->altsetting->bInterfaceNumber);
        printf("Device Filename : %s\n",dev->filename);
        printf("Bus Dir Name : %s\n",dbus->dirname);

        usb_get_string_simple(udev,dev->descriptor.iManufacturer,string,sizeof(string));
        printf("Device Manfucaturer : %s\n",string);
        usb_get_string_simple(udev,dev->descriptor.iProduct,string,sizeof(string));
        printf("Product Name : %s\n",string);
        usb_get_string_simple(udev,dev->descriptor.iSerialNumber,string,sizeof(string));
        printf("Device Serial Number: %s\n",string);
        printf("End point addresses : 0x%x\n",dev->config->interface->altsetting->endpoint->bEndpointAddress);

        int numBytes = 4;
        int errCount = 0;
        unsigned char endPoint = 0x81;
	int i;
	int tmp1 = 5;
	int tmp2 = 5;

        // reset usb device if command line numBytes = 0 and exit
        if (argc == 4 && atoi(argv[3]) == 0) { usb_reset(udev); exit(0); }

        // use command line value for numBytes if supplied
        if (argc == 4) numBytes = atoi(argv[3]);
        if (numBytes > 64) numBytes = 64;

        while (1) {  
                for (x=0; x<numBytes; x++)  string[x]=0;
                
                // read numBytes bytes using interrupt_read,
                r = usb_interrupt_read(udev, endPoint, string, numBytes, 0); 
                
                system("clear");
		
		tmp1+=string[1];
		tmp2+=string[2];
		if (tmp1>=0 && tmp2>=0 && tmp1<=150 && tmp2<=50){
		for (i=0;i<tmp2;i++) printf("\n");
		for (i=0;i<tmp1;i++) printf(".");
		if (string[0]==0) printf ("X");
		else if (string[0]==1) printf("L");
		else if(string[0]==2) printf("R");
		else if(string[0]==3) printf("LR");
		else if(string[0]==4) printf("M");
		}
		else {
		  if(tmp1<0) tmp1=0;
		  else if (tmp1>150) tmp1=150;
		  if (tmp2<0) tmp2=0;
		  else if (tmp2>50) tmp2=50;
		}
		if (string[3]==1) printf ("\nSCROLL UP");
		if (string[3]==-1) printf ("\nSCROLL DOWN");
		printf ("\n(%d, %d)",tmp1,tmp2);
		if ( r < 0 ) errCount++;
                if (errCount >= 100) break; 
        
                usb_clear_halt(udev,endPoint); 
        }

        printf("Closing Device.\n");
        usb_release_interface(udev,0);
        usb_close(udev);

        return EXIT_SUCCESS;
}

С помощью interrupt_read мы получаем 4 байта от каждого перемещения мыши. Вот за что они отвечают:
1 байт — нажатия клавиш. Равен 1 если нажата левая клавиша, 2 если правая, 3 если обе вместе, 4 если нажато колесо.
2 байт — горизонтальное перемещение. Чем больше значение, тем быстрее движется мышь.
3 байт — вертикальное перемещение. Аналогично с горизонтальным.
4 байт — скроллинг.

В левом верхнем углу выводятся координаты мыши; буква, обозначающая курсор, означает нажатую кнопку:
L — левая.
R — правая.
LR — левая+правая.
M — средняя.

При скроллинге рядом с курсором появляется надпись «Scroll up/down»

Ну и так выглядит в консоли информация о мыши:
image
и курсор:
image

Извиняюсь за некоторую кривость кода, писалось как всегда в последний момент, но это впервые курсовая работа была действительно интересной.
Спасибо за внимание.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.