精华内容
下载资源
问答
  • 串口键盘鼠标驱动开发

    热门讨论 2012-03-22 11:25:15
    简单介绍串口键盘鼠标驱动开发流程以及端口的应用说明.
  • Linux2.6内核的USB鼠标驱动开发; 用于linux2.6内核下USB鼠标驱动开发.
  • 鼠标驱动程序开发

    2013-09-05 00:24:53
    鼠标驱动程序开发,本段程序对于想要开发鼠标驱动的你,应该会比较有帮助。
  • 入口DriverEntry和键盘过滤类似,在Attach鼠标驱动有些区别 直接上代码: 有个问题:加载鼠标过滤驱动后提示“”连接系统上的设备没有发挥作用“”-虽不影响使用但会影响过滤驱动的卸载,希望某天有大神求解

    入口DriverEntry和键盘过滤类似,在Attach鼠标驱动有些区别 

    直接上代码:


    有个问题:加载鼠标过滤驱动后提示“”连接系统上的设备没有发挥作用“”-虽不影响使用但会影响过滤驱动的卸载,希望某天有大神求解

    展开全文
  • 鼠标驱动可分为几个部分:驱动加载部分、probe部分、open部分、urb回调函数处理部分。 一、驱动加载部分 static int __init usb_mouse_init(void) { int retval = usb_register(&usb_mouse_driver);//注册...

    参考2.6.14版本中的driver/usb/input/usbmouse.c。鼠标驱动可分为几个部分:驱动加载部分、probe部分、open部分、urb回调函数处理部分。

    一、驱动加载部分

    static int __init usb_mouse_init(void)
    { 	
    	int retval = usb_register(&usb_mouse_driver);//注册鼠标驱动 
    	if (retval == 0)
    	info(DRIVER_VERSION ":" DRIVER_DESC);
    	return retval;
    }
    
    其中usb_mouse_driver的定义为:
    
    static struct usb_driver usb_mouse_driver = {
    	.owner = THIS_MODULE,
    	.name = "usbmouse",
    	.probe = usb_mouse_probe,
    	.disconnect = usb_mouse_disconnect,
    	.id_table = usb_mouse_id_table,
    };
    
    

    如果注册成功的话,将会调用usb_mouse_probe。那么什么时候才算注册成功呢?
    和其它驱动注册过程一样,只有在其对应的“总线”上发现匹配的“设备”才会调用probe。总线匹配的方法和具体总线相关,如:platform_bus_type中是判断驱动名称和平台设备名称是否相同;那如何确认usb总线的匹配方法呢?

    Usb设备是注册在usb_bus_type总线下的。查看usb_bus_type的匹配方法。

    struct bus_type usb_bus_type = {
    	.name = "usb",
    	.match = usb_device_match,
        .hotplug = usb_hotplug,
    	.suspend = usb_generic_suspend,
    	.resume = usb_generic_resume,
    };
    

    其中usb_device_match定义了匹配方法

    static int usb_device_match (struct device *dev, struct device_driver *drv)
    {
    	struct usb_interface *intf;
    	struct usb_driver *usb_drv;
    	const struct usb_device_id *id;
    	/* check for generic driver, which we don't match any device with */
    	if (drv == &usb_generic_driver)
    		return 0;
    		
    	intf = to_usb_interface(dev);
    	usb_drv = to_usb_driver(drv);
    	id = usb_match_id (intf, usb_drv->id_table);
    	
    	if (id)
    		return 1;
    	return 0;
    }
    

    可以看出usb的匹配方法是usb_match_id (intf, usb_drv->id_table),
    也就是说通过比对“dev中intf信息”和“usb_drv->id_table信息”,
    如果匹配则说明驱动所对应的设备已经添加到总线上了,
    所以接下了就会调用drv中的probe方法注册usb设备驱动。

    usb_mouse_id_table的定义为:

    static struct usb_device_id usb_mouse_id_table[] = {
    	{ USB_INTERFACE_INFO(3, 1, 2) },
    	{ }     /* Terminating entry */
    };
     
    #define USB_INTERFACE_INFO(cl,sc,pr) /
    	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, /
    	.bInterfaceClass = (cl), /
    	.bInterfaceSubClass = (sc), /
    	.bInterfaceProtocol = (pr)
    

    鼠标设备遵循USB人机接口设备(HID),在HID规范中规定鼠标接口类码为:

    接口类:0x03
    接口子类:0x01
    接口协议:0x02

    这样分类的好处是设备厂商可以直接利用标准的驱动程序。除了HID类以外还有Mass storage、printer、audio等

    #define USB_DEVICE_ID_MATCH_INT_INFO /
         (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL)
    

    匹配的过程为:

    usb_match_id(struct usb_interface *interface, const struct usb_device_id *id)
    {
    	struct usb_host_interface *intf;
    	struct usb_device *dev;
    
    	/* proc_connectinfo in devio.c may call us with id == NULL. */
    	if (id == NULL)
    		return NULL;
    
    	intf = interface->cur_altsetting;
            dev = interface_to_usbdev(interface);
    
    	/* It is important to check that id->driver_info is nonzero,
    	   since an entry that is all zeroes except for a nonzero
    	   id->driver_info is the way to create an entry that
    	   indicates that the driver want to examine every
    	   device and interface. 
    	*/
    	
        for (; id->idVendor || id->bDeviceClass || id->bInterfaceClass || id->driver_info; id++) {
    
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
    			id->idVendor != le16_to_cpu(dev->descriptor.idVendor))
    			continue;
    
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
    			id->idProduct != le16_to_cpu(dev->descriptor.idProduct))
    			continue;
    
    		/* No need to test id->bcdDevice_lo != 0, since 0 is never greater than any unsigned number. */
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
    			(id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice)))
    			continue;
    
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
    			(id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice)))
    			continue;
    
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
    			(id->bDeviceClass != dev->descriptor.bDeviceClass))
    			continue;
    
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
    			(id->bDeviceSubClass!= dev->descriptor.bDeviceSubClass))
    			continue;
    
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
    			(id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
    			continue;
    
    		// 接口类
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
    			(id->bInterfaceClass != intf->desc.bInterfaceClass))
    			continue;
    
    		// 接口子类
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) &&
    			(id->bInterfaceSubClass != intf->desc.bInterfaceSubClass))
    			continue;
    
    		// 遵循的协议
    		if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) &&
    			(id->bInterfaceProtocol != intf->desc.bInterfaceProtocol))
    			continue;
    			
    		return id;
    	}
    	return NULL;
    }
    

    从中可以看出,只有当设备的接口类、接口子类、接口协议匹配鼠标驱动时鼠标驱动才会调用probe方法。


    二、probe部分

    static int usb_mouse_probe(struct usb_interface * intf, const struct usb_device_id * id)
    {
    	struct usb_device * dev = interface_to_usbdev(intf);
    	struct usb_host_interface *interface;
    	struct usb_endpoint_descriptor *endpoint;
    	struct usb_mouse *mouse;
    	int pipe, maxp;
    	char path[64];
     
    	interface = intf->cur_altsetting;
    	
    /* 以下是网络的一段对cur_altsettin的解释,下面就借花献佛。
    
    usb 设备有一个configuration 的概念,表示配置,一个设备可以有多个配置,但只能同时激活一个,
    如:一些设备可以下载固件,或可以设置不同的全局模式,就像手机可以被设定为静音模式或响铃模式一样。
    
    而这里又有一个setting,咋一看有些奇怪,这两个词不是一回事吗.
    
    还是拿我们最熟悉的手机来打比方,configuration 不说了,setting,一个手机可能各种配置都确定了,是振动还是铃声已经确定了,各种功能都确定了,但是声音的大小还可以变吧,通常手机的音量是一格一格的变动,大概也就5,6 格,那么这个可以算一个setting 吧.这里cur_altsetting 就是表示的当前的这个setting,或者说设置。
    
    可以查看原码中usb_interface 结构定义的说明部分。从说明中可以看到一个接口可以有多种setting*/
     
    	if (interface->desc.bNumEndpoints != 1)
    		return -ENODEV;
    
    /* 
    根据HID规则,期望鼠标只有一个端点即中断端点bNumEndpoints 就是接口描述符中的成员,
    表示这个接口有多少个端点,不过这其中不包括0 号端点,0号端点是任何一个usb 设备都必须是提供的,这个端点专门用于进行控制传输,即它是一个控制端点.
    正因为如此,所以即使一个设备没有进行任何设置,usb 主机也可以开始跟它进行一些通信,因为即使不知道其它的端点,但至少知道它一定有一个0号端点,或者说一个控制端点。
    */
    	endpoint = &interface->endpoint[0].desc;//端点0描述符,此处的0表示中断端点 
    	if (!(endpoint->bEndpointAddress & 0x80))
    		return -ENODEV;
     
    /*	先看bEndpointAddress,这个struct usb_endpoint_descriptor 中的一个成员,是8个bit,或者说1 个byte,
    	其中bit7 表示 *的是这个端点的方向,0 表示OUT,1 表示IN,OUT 与IN 是对主机而言。OUT 就是从主机到设备,IN 就是从设备到主机。
    	而宏 USB_DIR_IN 来自 include/linux/usb_ch9.h
     *  USB directions
     *  This bit flag is used in endpoint descriptors' bEndpointAddress field.
     *  It's also one of three fields in control requests bRequestType.
     *	#define USB_DIR_OUT 0 		/* to device * /
     *	#define USB_DIR_IN 0x80 	/* to host * / 
     */
     
    	if ((endpoint->bmAttributes & 3) != 3)? //判断是否是中断类型 
    		return -ENODEV;
    /* 	bmAttributes 表示属性,总共8位,其中bit1和bit0 共同称为Transfer Type,即传输类型,
    	即00 表示控制,	01 表示等时,		10 表示批量,		11 表示中断*/
     
    	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);	//构造中断端点的输入pipe
    	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
     
    ====================>>>
    /*	跟踪usb_maxpacket
    	usb_maxpacket(struct usb_device *udev, int pipe, int is_out)
    	{
    		struct usb_host_endpoint         *ep;
    		unsigned	epnum = usb_pipeendpoint(pipe);
    		
    		/ * 得到的自然就是原来pipe 里边的15 至18 位.
    		一个pipe 的15 位至18 位是endpoint 号,(一共16 个endpoint,)所以很显然,这里就是得到endpoint 号 * /							
    		if (is_out) {
    			WARN_ON(usb_pipein(pipe));
    			ep = udev->ep_out[epnum];
    		} else {
    			WARN_ON(usb_pipeout(pipe));
    			ep = udev->ep_in[epnum];
    		}
    		if (!ep)
    			return 0;
    		/ * NOTE:? only 0x07ff bits are for packet size... * /
    		return le16_to_cpu(ep->desc.wMaxPacketSize);
    	}
    */
    <<<====================
    
    	// 返回对应端点能够传输的最大的数据包,鼠标的返回的最大数据包为4个字节, 
    	// 第0个字节:bit 0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况 
    	// 第1个字节:表示鼠标的水平位移 
    	// 第2个字节:表示鼠标的垂直位移 
    	// 第3个字节:REL_WHEEL位移
     
    	if (!(mouse = kmalloc(sizeof(struct usb_mouse), GFP_KERNEL)))
    		return -ENOMEM;
    	memset(mouse, 0, sizeof(struct usb_mouse));
    	mouse->data = usb_buffer_alloc(dev, 8, SLAB_ATOMIC, &mouse->data_dma);
     
    	/*  申请用于urb用于数据传输的内存,注意:这里将返回“mouse->data”和“mouse->data_dma” 
    		mouse->data:记录了用于普通传输用的内存指针 
    		mouse->data_dma:记录了用于DMA传输的内存指针 
    		如果是DMA 方式的传输,那么usb core 就应该使用mouse->data_dma
    	*/
     
    	if (!mouse->data) {
    		kfree(mouse);
    		return -ENOMEM;
    	}
    	mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
    	
    	if (!mouse->irq) {
    		usb_buffer_free(dev, 8, mouse->data, mouse->data_dma);
    		kfree(mouse);
    		return -ENODEV;
    	}
    	mouse->usbdev = dev;
    	mouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
     
    	//设置input系统响应按键和REL(相对结果)事件
     
    	mouse->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
    	mouse->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y);
    	mouse->dev.keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
    	mouse->dev.relbit[0] |= BIT(REL_WHEEL);
     
    	//设置input系统响应的码表及rel表
     
    	mouse->dev.private = mouse;
    	mouse->dev.open = usb_mouse_open;
    	mouse->dev.close = usb_mouse_close;
     
    	usb_make_path(dev, path, 64);
    	sprintf(mouse->phys, "%s/input0", path);
     
    	mouse->dev.name = mouse->name;
    	mouse->dev.phys = mouse->phys;
    	usb_to_input_id(dev, &mouse->dev.id);
    
    ====================>>>
    /*
    	usb_to_input_id(const struct usb_device *dev, struct input_id *id)
    	{
    		id->bustype = BUS_USB;
    		id->vendor = le16_to_cpu(dev->descriptor.idVendor);
    		id->product = le16_to_cpu(dev->descriptor.idProduct);
    		id->version = le16_to_cpu(dev->descriptor.bcdDevice);
    	}
    struct usb_device 中有一个成员struct usb_device_descriptor,
    而struct usb_device_descriptor 中的成员__u16 bcdDevice,表示的是制造商指定的产品的版本号,制造商id 和产品id 来标志一个设备.
    bcdDevice 一共16 位,是以bcd码的方式保存的信息,也就是说,每4 位代表一个十进制的数,比如0011 0110 1001 0111 就代表的3697.
    
    业内为每家公司编一个号,这样便于管理,
    比如三星的编号就是0x0839,那么三星的产品中就会在其设备描述符中idVendor 的烙上0x0839.
    而三星自己的每种产品也会有个编号,和Digimax 410 对应的编号就是0x000a,
    而bcdDevice_lo 和bcdDevice_hi 共同组成一个具体设备的编号(device release number),
    bcd 就意味着这个编号是二进制的格式.
    */
    <<<====================
     
    	mouse->dev.dev = &intf->dev;
     
    	if (dev->manufacturer)
    		strcat(mouse->name, dev->manufacturer);
    	if (dev->product)
    		sprintf(mouse->name, "%s %s", mouse->name, dev->product);
     
    	if (!strlen(mouse->name))
    		sprintf(mouse->name, "USB HIDBP Mouse %04x:%04x",
                               mouse->dev.id.vendor, mouse->dev.id.product);
     
    	usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
                                        (maxp > 8 ? 8 : maxp),
                                        usb_mouse_irq, mouse, endpoint->bInterval);
     
    /*
    static inline void usb_fill_int_urb (struct urb *urb,
    		struct usb_device *dev,
    		unsigned int pipe,
    		void *transfer_buffer,
    		int buffer_length,
    		usb_complete_t complete,
    		void *context,
    		int interval)
    {
    	spin_lock_init(&urb->lock);
    	urb->dev = dev;
    	urb->pipe = pipe;
    	urb->transfer_buffer = transfer_buffer;
    	//如果不使用DMA传输方式,则使用这个缓冲指针。如何用DMA则使用transfer_DMA,这个值会在后面单独给URB赋
    	
    	urb->transfer_buffer_length = buffer_length;
    	urb->complete = complete;
    	urb->context = context;
    	if (dev->speed == USB_SPEED_HIGH)
    		urb->interval = 1 << (interval - 1);
    	else
    		urb->interval = interval;
    		urb->start_frame = -1;
    	}
    	此处只是构建好一个urb,在open方法中会实现向usb core递交urb
    */
     
    	mouse->irq->transfer_dma = mouse->data_dma;
    	mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
     
    /*
    #define URB_NO_TRANSFER_DMA_MAP 0x0004? //urb->transfer_dma valid on submit 
    #define URB_NO_SETUP_DMA_MAP??? 0x0008? //urb->setup_dma valid on submit 
    这里是两个DMA 相关的flag,一个是URB_NO_SETUP_DMA_MAP,而另一个是URB_NO_TRANSFER_DMA_MAP.
    注意这两个是不一样的,前一个是专门为控制传输准备的,因为只有控制传输需要有这么一个setup 阶段需要准备一个setup packet。 
    
    transfer_buffer 是给各种传输方式中真正用来数据传输的,而setup_packet 仅仅是在控制传输中发送setup 的包,控制传输除了setup 阶段之外,也会有数据传输阶段,这一阶段要传输数据还是得靠transfer_buffer,而如果使用dma 方式,那么就是使用transfer_dma.
    
    因为这里使用了mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP,所以应该给urb的transfer_dma赋值。所以用了:
    mouse->irq->transfer_dma = mouse->data_dma;
    */
     
    	input_register_device(&mouse->dev);
    	//向input系统注册input设备
     
    	printk(KERN_INFO "input: %s on %s/n", mouse->name, path);
     
    	usb_set_intfdata(intf, mouse);
     
    /*
    	usb_set_intfdata().的结果就是使得 %intf->dev->driver_data= mouse,
    	而其它函数中会调用usb_get_intfdata(intf)的作用就是把mouse从中取出来
    */
    	return 0;
    }
    

    三、open部分

    当应用层打开鼠标设备时,usb_mouse_open将被调用

    static int usb_mouse_open(struct input_dev *dev)
    {
    	struct usb_mouse *mouse = dev->private;
    	mouse->irq->dev = mouse->usbdev;
    	if (usb_submit_urb(mouse->irq, GFP_KERNEL))
    		return -EIO;
    	//向usb core递交了在probe中构建好的中断urb,注意:此处是成功递交给usb core以后就返回,而不是等到从设备取得鼠标数据。
     
    	return 0;
    }
    

    四、urb回调函数处理部分

    当出现传输错误或获取到鼠标数据后,urb回调函数将被执行

    static void usb_mouse_irq(struct urb *urb, struct pt_regs *regs)
    {
             struct usb_mouse *mouse = urb->context;
     
    		//在usb_fill_int_urb中有对urb->context赋值
     
             signed char *data = mouse->data;
             struct input_dev *dev = &mouse->dev;
             int status;
             switch (urb->status) {
             	case 0:     		/* success */
                      break;
             	case -ECONNRESET:	/* unlink */
             	case -ENOENT:
             	case -ESHUTDOWN:
                      return;
             						/* -EPIPE:? should clear the halt */
             	default:         	/* error */
                      goto resubmit;
    		 }
     
    		 input_regs(dev, regs);
     
    		 input_report_key(dev, BTN_LEFT,        data[0] & 0x01);
             input_report_key(dev, BTN_RIGHT,       data[0] & 0x02);
             input_report_key(dev, BTN_MIDDLE,      data[0] & 0x04);
             input_report_key(dev, BTN_SIDE,        data[0] & 0x08);
             input_report_key(dev, BTN_EXTRA,       data[0] & 0x10);
             //向input系统报告key事件,分别是鼠标LEFT、RIGHT、MIDDLE、SIDE、EXTRA键,
             
             static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)中的value非0时表示按下,0表示释放 
             input_report_rel(dev, REL_X,         data[1]);
             input_report_rel(dev, REL_Y,         data[2]);
             input_report_rel(dev, REL_WHEEL, data[3]);
             //向input系统报告rel事件,分别是x方向位移、y方向位移、wheel值 
             
             input_sync(dev);
             //最后需要向事件接受者发送一个完整的报告。这是input系统的要求。 
             
             resubmit:
             status = usb_submit_urb (urb, SLAB_ATOMIC);
             //重新递交urb
             
             if (status)
             	err ("can't resubmit intr, %s-%s/input0, status %d",
                	mouse->usbdev->bus->bus_name,mouse->usbdev->devpath, status);
    }
    

    五、应用层测试代码编写

    在应用层编写测试鼠标的测试程序,在我的系统中,鼠标设备为/dev/input/event3. 测试代码如下:

    /*
     * usb_mouse_test.c
     * by lht
     */
     
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <linux/input.h>
     
    int main (void) 
    {
    	int fd,i,count;
    	struct input_event ev_mouse[2];
    	fd = open ("/dev/input/event3",O_RDWR);
    	if (fd < 0) {
    		printf ("fd open failed/n");
    		exit(0);
    	}
    	printf ("/nmouse opened, fd=%d/n",fd);
    	while(1)
    	{
    		printf(".............................................../n");
    		count=read(fd, ev_mouse, sizeof(struct input_event));
    		for(i=0;i<(int)count/sizeof(struct input_event);i++)
    		{
    			printf("type=%d/n",ev_mouse[i].type);
     			if(EV_REL==ev_mouse[i].type)
     			{
    				printf("time:%ld.%d",ev_mouse[i].time.tv_sec,ev_mouse[i].time.tv_usec);
    				printf(" type:%d code:%d value:%d/n",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);
    			}
    			if(EV_KEY==ev_mouse[i].type)
    			{
    				printf("time:%ld.%d",ev_mouse[i].time.tv_sec,ev_mouse[i].time.tv_usec);
    				printf(" type:%d code:%d value:%d/n",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);
    			}
    		}
    	}
    	close (fd);
    	return 0;
    }
    
    





    本文学自: 《Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试

    展开全文
  • magic mouse驱动for win10是一款苹果自主研发的鼠标驱动,者为大家提供的支持win10和mac操作系统。驱动下载时可以选择32位和64位,需要的用户可以免费下载。 Magic Mouse驱动官方介绍: Magic Mouse是苹果自行开发的...
  • 鼠标驱动程序源代码,windows下WDM开发.zip
  • 鼠标驱动可分为几个部分:驱动加载部分、probe部分、open部分、urb回调函数处理部分。  一、驱动加载部分static int __init usb_mouse_init(void) { int retval = usb_register(&usb_mouse_driver);//注册鼠标...

    参考2.6.14版本中的driver/usb/input/usbmouse.c。鼠标驱动可分为几个部分:驱动加载部分、probe部分、open部分、urb回调函数处理部分。 


    一、驱动加载部分

    static int __init usb_mouse_init(void)
    { 	
    	int retval = usb_register(&usb_mouse_driver);//注册鼠标驱动 
    	if (retval == 0)
    	info(DRIVER_VERSION ":" DRIVER_DESC);
    	return retval;
    }
    
    其中usb_mouse_driver的定义为:
    static struct usb_driver usb_mouse_driver = {
    	.owner = THIS_MODULE,
    	.name = "usbmouse",
    	.probe = usb_mouse_probe,
    	.disconnect = usb_mouse_disconnect,
    	.id_table = usb_mouse_id_table,
    };

     

          如果注册成功的话,将会调用usb_mouse_probe。那么什么时候才算注册成功呢?

          和其它驱动注册过程一样,只有在其对应的“总线”上发现匹配的“设备”才会调用probe。总线匹配的方法和具体总线相关,如:platform_bus_type中是判断驱动名称和平台设备名称是否相同;那如何确认usb总线的匹配方法呢?

           Usb设备是注册在usb_bus_type总线下的。查看usb_bus_type的匹配方法。

    struct bus_type usb_bus_type = {
    	.name = "usb",
    	.match = usb_device_match,
            .hotplug = usb_hotplug,
    	.suspend = usb_generic_suspend,
    	.resume = usb_generic_resume,
    };
    其中usb_device_match定义了匹配方法
    static int usb_device_match (struct device *dev, struct device_driver *drv)
    {
                        struct usb_interface *intf;
                        struct usb_driver *usb_drv;
                        const struct usb_device_id *id;
                        /* check for generic driver, which we don't match any device with */
                        if (drv == &usb_generic_driver)
                        return 0;
                        intf = to_usb_interface(dev);
                        usb_drv = to_usb_driver(drv);
                        id = usb_match_id (intf, usb_drv->id_table);
                        if (id)
                                  return 1;
                        return 0;
    }

    可以看出usb的匹配方法是usb_match_id (intf, usb_drv->id_table),也就是说通过比对“dev中intf信息”和“usb_drv->id_table信息”,如果匹配则说明驱动所对应的设备已经添加到总线上了,所以接下了就会调用drv中的probe方法注册usb设备驱动。

    usb_mouse_id_table的定义为:

    static struct usb_device_id usb_mouse_id_table[] = {
                        { USB_INTERFACE_INFO(3, 1, 2) },
                        { }                              /* Terminating entry */
              };
    
    #define USB_INTERFACE_INFO(cl,sc,pr) /
              .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, /
              .bInterfaceClass = (cl), /
              .bInterfaceSubClass = (sc), /
              .bInterfaceProtocol = (pr)

    鼠标设备遵循USB人机接口设备(HID),在HID规范中规定鼠标接口类码为:

    接口类:0x03
    接口子类:0x01
    接口协议:0x02

    这样分类的好处是设备厂商可以直接利用标准的驱动程序。除了HID类以外还有Mass storage、printer、audio等

    #define USB_DEVICE_ID_MATCH_INT_INFO /
                        (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL)

    匹配的过程为:

    usb_match_id(struct usb_interface *interface, const struct usb_device_id *id)
              {
                        struct usb_host_interface *intf;
                        struct usb_device *dev;
    
              /* proc_connectinfo in devio.c may call us with id == NULL. */
                        if (id == NULL)
                                  return NULL;
    
              intf = interface->cur_altsetting;
                      dev = interface_to_usbdev(interface);
    
              /* It is important to check that id->driver_info is nonzero,
                        since an entry that is all zeroes except for a nonzero
                        id->driver_info is the way to create an entry that
                        indicates that the driver want to examine every
                        device and interface. */
                      for (; id->idVendor || id->bDeviceClass || id->bInterfaceClass ||
                               id->driver_info; id++) {
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
                                     id->idVendor != le16_to_cpu(dev->descriptor.idVendor))
                                     continue;
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
                                     id->idProduct != le16_to_cpu(dev->descriptor.idProduct))
                                     continue;
    
                       /* No need to test id->bcdDevice_lo != 0, since 0 is never greater than any unsigned number. */
                                if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
                                    (id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice)))
                                   continue;
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
                                     (id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice)))
                                     continue;
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
                                     (id->bDeviceClass != dev->descriptor.bDeviceClass))
                                     continue;
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
                                     (id->bDeviceSubClass!= dev->descriptor.bDeviceSubClass))
                                     continue;
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
                                     (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
                                     continue;
    
                        //接口类
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
                                    (id->bInterfaceClass != intf->desc.bInterfaceClass))
                                    continue;
    
                        //接口子类
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) &&
                                    (id->bInterfaceSubClass != intf->desc.bInterfaceSubClass))
                                    continue;
    
                      //遵循的协议
    
                        if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) &&
                                    (id->bInterfaceProtocol != intf->desc.bInterfaceProtocol))
                                    continue;
    
                      return id;
                          }
                          return NULL;
               }
    
    
    从中可以看出,只有当设备的接口类、接口子类、接口协议匹配鼠标驱动时鼠标驱动才会调用probe方法。


    二、probe部分

    static int usb_mouse_probe(struct usb_interface * intf, const struct usb_device_id * id)
    {
             struct usb_device * dev = interface_to_usbdev(intf);
             struct usb_host_interface *interface;
             struct usb_endpoint_descriptor *endpoint;
             struct usb_mouse *mouse;
             int pipe, maxp;
             char path[64];
    
             interface = intf->cur_altsetting;
    
    /* 以下是网络的一段对cur_altsettin的解释,下面就借花献佛。usb 设备有一个configuration 的概念,表示配置,一个设备可以有多个配置,但只能同时激活一个,如:一些设备可以下载固件,或可以设置不同的全局模式,就像手机可以被设定为静音模式或响铃模式一样。而这里又有一个setting,咋一看有些奇怪,这两个词不是一回事吗.还是拿我们最熟悉的手机来打比方,configuration 不说了,setting,一个手机可能各种配置都确定了,是振动还是铃声已经确定了,各种功能都确定了,但是声音的大小还可以变吧,通常手机的音量是一格一格的变动,大概也就5,6 格,那么这个可以算一个setting 吧.这里cur_altsetting 就是表示的当前的这个setting,或者说设置。可以查看原码中usb_interface 结构定义的说明部分。从说明中可以看到一个接口可以有多种setting*/
    
             if (interface->desc.bNumEndpoints != 1)
    		return -ENODEV;
    
    /*根据HID规则,期望鼠标只有一个端点即中断端点bNumEndpoints 就是接口描述符中的成员,表示这个接口有多少个端点,不过这其中不包括0 号端点,0号端点是任何一个usb 设备都必须是提供的,这个端点专门用于进行控制传输,即它是一个控制端点.正因为如此,所以即使一个设备没有进行任何设置,usb 主机也可以开始跟它进行一些通信,因为即使不知道其它的端点,但至少知道它一定有一个0号端点,或者说一个控制端点。
             */
    
             endpoint = &interface->endpoint[0].desc;//端点0描述符,此处的0表示中断端点 
             if (!(endpoint->bEndpointAddress & 0x80))
    		return -ENODEV;
    
    /*先看bEndpointAddress,这个struct usb_endpoint_descriptor 中的一个成员,是8个bit,或者说1 个byte,其中bit7 表示 *的是这个端点的方向,0 表示OUT,1 表示IN,OUT 与IN 是对主机而言。OUT 就是从主机到设备,IN 就是从设备到主机。而宏 
     *USB_DIR_IN 来自
     *include/linux/usb_ch9.h
     * USB directions
     * This bit flag is used in endpoint descriptors' bEndpointAddress field.
     * It's also one of three fields in control requests bRequestType.
     *#define USB_DIR_OUT 0 /* to device */
     *#define USB_DIR_IN 0x80 /* to host */ 
     */
    
    	if ((endpoint->bmAttributes & 3) != 3)? //判断是否是中断类型 
    		return -ENODEV;
    
    /* bmAttributes 表示属性,总共8位,其中bit1和bit0 共同称为Transfer Type,即传输类型,即00 表示控制,01 表示等时,10 表示批量,11 表示中断*/
    
    	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//构造中断端点的输入pipe
    
    	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
    
    /*跟踪usb_maxpacket
    	usb_maxpacket(struct usb_device *udev, int pipe, int is_out)
    	{
    		struct usb_host_endpoint         *ep;
    		unsigned                  epnum = usb_pipeendpoint(pipe);
    		/*
                      得到的自然就是原来pipe 里边的15 至18 位.一个pipe 的15 位至18 位是endpoint 号,(一共16 个endpoint,)所以很显然,这里就是得到endpoint 号 
                      */
    		if (is_out) {
    			WARN_ON(usb_pipein(pipe));
    			ep = udev->ep_out[epnum];
    		} else {
    			WARN_ON(usb_pipeout(pipe));
    			ep = udev->ep_in[epnum];
    		}
    		if (!ep)
    			return 0;
                      /* NOTE:? only 0x07ff bits are for packet size... */
    			return le16_to_cpu(ep->desc.wMaxPacketSize);
             }
             */
             //返回对应端点能够传输的最大的数据包,鼠标的返回的最大数据包为4个字节, 
             第0个字节:bit 0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况 
             第1个字节:表示鼠标的水平位移 
             第2个字节:表示鼠标的垂直位移 
             第3个字节:REL_WHEEL位移
    
    	if (!(mouse = kmalloc(sizeof(struct usb_mouse), GFP_KERNEL)))
    		return -ENOMEM;
    	memset(mouse, 0, sizeof(struct usb_mouse));
    	mouse->data = usb_buffer_alloc(dev, 8, SLAB_ATOMIC, &mouse->data_dma);
    
    	/*
             申请用于urb用于数据传输的内存,注意:这里将返回“mouse->data”和“mouse->data_dma” 
             mouse->data:记录了用于普通传输用的内存指针 
             mouse->data_dma:记录了用于DMA传输的内存指针 
             如果是DMA 方式的传输,那么usb core 就应该使用mouse->data_dma
             */
    
    	if (!mouse->data) {
    		kfree(mouse);
    		return -ENOMEM;
             }
             mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
             if (!mouse->irq) {
    		usb_buffer_free(dev, 8, mouse->data, mouse->data_dma);
    		kfree(mouse);
    		return -ENODEV;
             }
             mouse->usbdev = dev;
             mouse->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
    
    	//设置input系统响应按键和REL(相对结果)事件
    
    	mouse->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
    	mouse->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y);
    	mouse->dev.keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
    	mouse->dev.relbit[0] |= BIT(REL_WHEEL);
    
    	//设置input系统响应的码表及rel表
    
    	mouse->dev.private = mouse;
    	mouse->dev.open = usb_mouse_open;
    	mouse->dev.close = usb_mouse_close;
    
    	usb_make_path(dev, path, 64);
    	sprintf(mouse->phys, "%s/input0", path);
    
    	mouse->dev.name = mouse->name;
    	mouse->dev.phys = mouse->phys;
    	usb_to_input_id(dev, &mouse->dev.id);
    
    /*
    	usb_to_input_id(const struct usb_device *dev, struct input_id *id)
    	{
                      id->bustype = BUS_USB;
                      id->vendor = le16_to_cpu(dev->descriptor.idVendor);
                      id->product = le16_to_cpu(dev->descriptor.idProduct);
                      id->version = le16_to_cpu(dev->descriptor.bcdDevice);
             }
    
    struct usb_device 中有一个成员struct usb_device_descriptor,而struct usb_device_descriptor 中的成员__u16 bcdDevice,表示的是制造商指定的产品的版本号,制造商id 和产品id 来标志一个设备.bcdDevice 一共16 位,是以bcd码的方式保存的信息,也就是说,每4 位代表一个十进制的数,比如0011 0110 1001 0111 就代表的3697.
    
    业内为每家公司编一个号,这样便于管理,比如三星的编号就是0x0839,那么三星的产品中就会在其设备描述符中idVendor 的烙上0x0839.而三星自己的每种产品也会有个编号,和Digimax 410 对应的编号就是0x000a,而bcdDevice_lo 和bcdDevice_hi 共同组成一个具体设备的编号(device release
    number),bcd 就意味着这个编号是二进制的格式.
             */
    
    	mouse->dev.dev = &intf->dev;
    
    	if (dev->manufacturer)
    		strcat(mouse->name, dev->manufacturer);
    	if (dev->product)
    		sprintf(mouse->name, "%s %s", mouse->name, dev->product);
    
    	if (!strlen(mouse->name))
    		sprintf(mouse->name, "USB HIDBP Mouse %04x:%04x",
                               mouse->dev.id.vendor, mouse->dev.id.product);
    
    	usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
                                        (maxp > 8 ? 8 : maxp),
                                        usb_mouse_irq, mouse, endpoint->bInterval);
    
    /*
             static inline void usb_fill_int_urb (struct urb *urb,
                                            struct usb_device *dev,
                                            unsigned int pipe,
                                            void *transfer_buffer,
                                            int buffer_length,
                                            usb_complete_t complete,
                                            void *context,
                                            int interval)
             {
                      spin_lock_init(&urb->lock);
                      urb->dev = dev;
                      urb->pipe = pipe;
                      urb->transfer_buffer = transfer_buffer;//如果不使用DMA传输方式,则使用这个缓冲指针。如何用DMA则使用transfer_DMA,这个值会在后面单独给URB赋
    
             urb->transfer_buffer_length = buffer_length;
                      urb->complete = complete;
                      urb->context = context;
                      if (dev->speed == USB_SPEED_HIGH)
                               urb->interval = 1 << (interval - 1);
                      else
                               urb->interval = interval;
                      urb->start_frame = -1;
    
    	}
    
    	此处只是构建好一个urb,在open方法中会实现向usb core递交urb
             */
    
    	mouse->irq->transfer_dma = mouse->data_dma;
    	mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    
    /*
             #define URB_NO_TRANSFER_DMA_MAP 0x0004? //urb->transfer_dma valid on submit 
             #define URB_NO_SETUP_DMA_MAP??? 0x0008? //urb->setup_dma valid on submit 
    ,         这里是两个DMA 相关的flag,一个是URB_NO_SETUP_DMA_MAP,而另一个是 
             URB_NO_TRANSFER_DMA_MAP.注意这两个是不一样的,前一个是专门为控制传输准备的,因为只有控制传输需要有这么一个setup 阶段需要准备一个setup packet。 
             transfer_buffer 是给各种传输方式中真正用来数据传输的,而setup_packet 仅仅是在控制传输中发送setup 的包,控制传输除了setup 阶段之外,也会有数据传输阶段,这一阶段要传输数据还是得靠transfer_buffer,而如果使用dma 方式,那么就是使用transfer_dma.
             因为这里使用了mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP,所以应该给urb的transfer_dma赋值。所以用了:
             mouse->irq->transfer_dma = mouse->data_dma;
             */
    
    	input_register_device(&mouse->dev);
    
    	//向input系统注册input设备
    
    	printk(KERN_INFO "input: %s on %s/n", mouse->name, path);
    
    	usb_set_intfdata(intf, mouse);
    
    /*
    	usb_set_intfdata().的结果就是使得 
             %intf->dev->driver_data= mouse,而其它函数中会调用usb_get_intfdata(intf)的作用就是把mouse从中取出来
             */
    
    	return 0;
    }



    三、open部分

    当应用层打开鼠标设备时,usb_mouse_open将被调用

    static int usb_mouse_open(struct input_dev *dev)
    {
             struct usb_mouse *mouse = dev->private;
    
             mouse->irq->dev = mouse->usbdev;
             if (usb_submit_urb(mouse->irq, GFP_KERNEL))
                      return -EIO;
    
    //向usb core递交了在probe中构建好的中断urb,注意:此处是成功递交给usb core以后就返回,而不是等到从设备取得鼠标数据。
    
             return 0;
    }


    四、urb回调函数处理部分

    当出现传输错误或获取到鼠标数据后,urb回调函数将被执行 

    static void usb_mouse_irq(struct urb *urb, struct pt_regs *regs)
    {
             struct usb_mouse *mouse = urb->context;
    
    //在usb_fill_int_urb中有对urb->context赋值
    
             signed char *data = mouse->data;
             struct input_dev *dev = &mouse->dev;
             int status;
             switch (urb->status) {
             case 0:                  /* success */
                      break;
             case -ECONNRESET:         /* unlink */
             case -ENOENT:
             case -ESHUTDOWN:
                      return;
             /* -EPIPE:? should clear the halt */
             default:         /* error */
                      goto resubmit;
    }
    
    input_regs(dev, regs);
    
    input_report_key(dev, BTN_LEFT,         data[0] & 0x01);
             input_report_key(dev, BTN_RIGHT,         data[0] & 0x02);
             input_report_key(dev, BTN_MIDDLE,      data[0] & 0x04);
             input_report_key(dev, BTN_SIDE,         data[0] & 0x08);
             input_report_key(dev, BTN_EXTRA,         data[0] & 0x10);
             //向input系统报告key事件,分别是鼠标LEFT、RIGHT、MIDDLE、SIDE、EXTRA键,
             static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)中的value非0时表示按下,0表示释放 
             input_report_rel(dev, REL_X,         data[1]);
             input_report_rel(dev, REL_Y,         data[2]);
             input_report_rel(dev, REL_WHEEL, data[3]);
             //向input系统报告rel事件,分别是x方向位移、y方向位移、wheel值 
             input_sync(dev);
             //最后需要向事件接受者发送一个完整的报告。这是input系统的要求。 
             resubmit:
             status = usb_submit_urb (urb, SLAB_ATOMIC);
             //重新递交urb
             if (status)
                      err ("can't resubmit intr, %s-%s/input0, status %d",
                               mouse->usbdev->bus->bus_name,
                               mouse->usbdev->devpath, status);
    }


    五、应用层测试代码编写

    在应用层编写测试鼠标的测试程序,在我的系统中,鼠标设备为/dev/input/event3. 测试代码如下:

    /*
     * usb_mouse_test.c
     * by lht
     */
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <linux/input.h>
    
    int main (void) 
    {
    	int fd,i,count;
    	struct input_event ev_mouse[2];
    	fd = open ("/dev/input/event3",O_RDWR);
    	if (fd < 0) {
    		printf ("fd open failed/n");
    		exit(0);
    	}
    	printf ("/nmouse opened, fd=%d/n",fd);
    	while(1)
    	{
    		printf(".............................................../n");
    		count=read(fd, ev_mouse, sizeof(struct input_event));
    		for(i=0;i<(int)count/sizeof(struct input_event);i++)
    		{
    			printf("type=%d/n",ev_mouse[i].type);
     			if(EV_REL==ev_mouse[i].type)
     			{
    				printf("time:%ld.%d",ev_mouse[i].time.tv_sec,ev_mouse[i].time.tv_usec);
    				printf(" type:%d code:%d value:%d/n",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);
    			}
    			if(EV_KEY==ev_mouse[i].type)
    			{
    				printf("time:%ld.%d",ev_mouse[i].time.tv_sec,ev_mouse[i].time.tv_usec);
    				printf(" type:%d code:%d value:%d/n",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);
    			}
    		}
    	}
    	close (fd);
    	return 0;
    }



    展开全文
  • VBA开发环境中鼠标滚轮驱动,VBA开发环境中鼠标滚轮驱动,VBA开发环境中鼠标滚轮驱动
  • 鼠标映射虚拟位置传感器的驱动开发模型,把鼠标虚拟成自定义的HID设备
  • usb鼠标驱动

    2012-06-28 10:05:45
    驱动开发usb类型鼠标所使用,包含添加设备等多个例程。有详细的代码描述内容。
  • by fanxiushu 2017-10-27 转载或引用请注明原始作者。...于是决定重新开发macbook pro 2017触摸板的windows驱动。 请稍后关注 GITHUB和CSDN提供的源代码和驱动程序。 如下连接, http://blog.csdn.net
                                                         by fanxiushu 2017-10-27 转载或引用请注明原始作者。


    做这个驱动,写这篇文章的目的就是因为macBook pro 2017版的触摸板在windows平台下难用,

    于是决定重新开发macbook pro 2017触摸板的windows驱动。

    已经开发好的驱动和源代码下载地址:

    GITHUB:   https://github.com/fanxiushu/kmouse_filter-AppleSPITrack-driver

    CSDN:   http://download.csdn.net/download/fanxiushu/10047600


    如下连接,
    http://blog.csdn.net/fanxiushu/article/details/78186745
    因为新近换的电脑是macbook pro 2017 13寸带bar的机器,其他还能将就,
    就是触摸板难用,动不动就弹出右键菜单,经常误触,
    而且尤其不爽的是拖动时候,必须按住边缘,另一只手指才能拖动,
    触摸板这么大,基本无法单手进行拖动操作,需要另一个手的手指按住触摸板边缘,然后拖动。
    虽然可以点击两次,第2次不离开触摸板来拖动,但是非常不习惯这种手势。
    还是习惯传统的食指按住触摸板,中指来拖动(因为这种手势更加接近按住鼠标左键移动鼠标的效果)。

    像我这种经常把电脑放到大腿上或者床上的人(反正就是不会老老实实的放到电脑桌上),
    如果带个鼠标是很不方便的,只能依赖触摸板来控制电脑。
    而且我的要求也不高,不需要什么多手势,只要触摸板能尽量模拟鼠标的效果就行了。
    其实个人觉得windows本身的易操作性,使用鼠标的效果就能控制操作系统的所有的东西了,
    手势多了我也记不住,每次切换不同手势还得思考一下也挺累(也许是还没习惯,也懒得去习惯了)。

    基于以上各种原因,于是决定重新开发这款电脑的触摸板驱动。
    首先解释一下“点按”和”轻点“:
    按住或重压,就是按下去,能听到“哒”的一声响;
    ”点按“就是“哒”的一声按下去然后立马弹上来,
    还有一个就是”轻点“,就是手指接触到触摸板然后迅速离开。

    我需要达到的效果也非常简单明了,用两根手指模拟鼠标效果,操作整个系统。
    1,首先一根手指按住触摸板的任意位置(是任意位置而不是触摸板边缘),另一根手指在触摸板上移动来达到拖动效果,
    这就相当于按住鼠标左键,移动鼠标的效果一样。
    2,一根手指轻点,相当于鼠标左键按下去然后立马弹上来。
         一根手指按住触摸板,相当于按下鼠标左键,从触摸板弹上来相当于弹出左键。
    3,两根手指同时轻点,相当于鼠标右键按下去然后立马弹上来。
         一根手指按住触摸板右边四分之三到四分之四部分(是整个右边3/4,而不是右下边缘),相当于按住鼠标右键。
    4,两根手指同时在触摸板移动,相当于滚轮滚动。
    5,另外再附加实现一个功能:三指拖移。等于是第1个功能的效果:一个手指按住,另一个手指移动。
          这样不重压触摸板的也能操作整个系统。

    不模拟鼠标的中间键按下效果,好像中间键没啥用处。

    以上就是我的简单明了的要求,当然习惯各种手势的你可能并不赞同,甚至有点嗤之以鼻,然而这依然是我的简单明了的要求。
    重新开发的触摸板驱动也只实现上边的功能而已。

    要重新开发macbook pro 2017年 13寸带bar(以下简称mbp2017)的触摸板的windows驱动,
    首先需要解决两件事:
    一,采集mbp2017的触摸板数据,
    二,模拟开发鼠标驱动。
    第二个问题100%的确保能解决,使用HID模式的鼠标驱动就可以了。
    关键是第一个问题,如果不能采集和解析触摸板的数据,基本就没戏了,只能老老实实的使用那个难用的原装驱动程序。
    mbp2017的触摸板数据结构格式是没有公开的,而且未来也很可能被苹果公司改变,因此没有通用性,
    我测试的对应bootcamp版本是 6.1.6813,对应的触摸板总线驱动的驱动日期是 2016/05/26, 版本 6.1.6500.0,
    其他版本的没测试过,所以不知道的数据格式是不是不同。

    既然这个数据没有公开,我们就必须要自己来采集和分析触摸板的数据,
    好在触摸板这类设备本身的数据量不大,数据结构也应该不会多复杂,只要Apple公司没变态到做加密,估计是能解析出来的。
    自己动手解析之前,先要搞清楚它的驱动运行流程。
    苹果使用的SPI总线来传输触摸板和键盘的数据,SPI总线接口,也是我在接触mbp2017时候才发现还有这么一个玩意,真是孤陋寡闻了。
    SPI 全称Serial Peripheral Interface--串行外设接口, 最初是Motorola提出和开发的,它使用主从模式通讯,这点跟USB有点像,
    同时通讯也很简单,比RS232(串口)还简单,所以不论软件或硬件成本都比较低。
    但是应付键盘和触摸板这类不需要大量通讯数据的器件完全足够了。更详细的关于SPI介绍,请查询其他资料。
    mbp2017的电脑windows驱动中关于SPI的,首先有个SPI总线驱动,在SPI总线驱动下挂载两个位置,
    位置1是键盘驱动,位置2才是触摸板驱动。详细可看下边的图示:


    画红线的部分,在”系统设备“里边的 “Apple SPI Device ” 就是总线驱动,它负责给键盘和触摸板的功能驱动提供数据,
    在“人体学输入设备”里边的“ Apple SPI Keyboard” 和 “ Apple SPI Trackpad” 对应的就是 键盘的功能驱动和触摸板的功能驱动,
    再看“Apple SPI Trackpad” 的属性, 它在总线驱动的位置是 2, 我们要做的事情,就是替换这个功能驱动,使用我们自己开发的驱动来代替。

    另外我们顺便看看这款电脑内置的USB接口的设备,
    上边的蓝线部分,在 “通用串行总线控制器” 里边存在一个“Apple USB Composite Device”的复合设备,
    这个复合设备管理着三个设备,“Apple Touch Bar”就是其中一个,这个就是multi-touch bar, 在windows平台下没啥用的鸡肋。
    另外两个看下图所示:


    Apple USB Composite Device一共包含三个设备:
    FaceTime HD Camera,
    Apple Touch Bar,
    USB接口的光感氛围器,就是检测环境光线强弱,从而自动调节屏幕的亮度。
    看图示的最下边, bNumConfigurations 是 3, 也就是三个配置描述符,分别对应这三种设备。

    正如上篇文章所说的,在单纯只安装windows系统的时候,这个“Apple USB Composite Device” 设备不能被发现,
    从而造成 摄像头,multi-touch bar,和光感器件找不到,也不知道苹果在设计硬件的时候搞了什么。
    估计得保留安装苹果系统时候的那个EFI Parttion 分区,才能被识别,只是后来没再折腾了,保留了MacOS系统。

    再回到触摸板驱动,我们打开注册表,在 如下的位置:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\SPI\VID_05ac&PID_0277&MI_02
    能找到这个驱动的安装信息,
    注意:mbp2017 对应的触摸板的PID是0277, VID是05ac, 不同型号的,可能PID会不同,这个得看具体机器。


    如上图所示,在里边的LowerFilters字段里是 苹果的 AppleSPITrackpad驱动,
    Service字段是 mshidkmdf ,从这信息,
    我们立马就知道 AppleSPITrackpad 是一个KMDF模型的驱动程序,并且属于标准的HID的KMDF程序。
    AppleSPITrackpad负责获取上边提到的 “Apple SPI Device" 总线驱动发上来的触摸板数据,
    并且解析模拟成标准的兼容windows的 HID鼠标驱动数据。


    AppleSPITrackpad在解析触摸板数据时候,提供了让人难以适应(至少我比较难适应)的鼠标动作模拟,
    因此重新开发这个驱动来解决这个问题。
    至此,我们大致知道他们的驱动的工作流程了。

    现在的任务首先就是获取SPI总线驱动发上来的数据结构格式。
    可以开发一个简单的WDM 过滤驱动,挂载到 LowerFilters字段里边的 AppleSPITrackpad 前边。
    这样AppleSPITrackpad跟SPI总线驱动通讯的所有IRP请求都能被截获到,从而就能获取到触摸板数据。
    WDM Filter的开发可以查看我很早前的一篇文章:
    http://blog.csdn.net/fanxiushu/article/details/8834385
    当然如果你能找到其他现成的工具来分析AppleSPITrackpad的通讯数据,会更快捷。

    通过分析,AppleSPITrackpad发给SPI总线驱动四个请求:
    IOCTL_HID_GET_DEVICE_DESCRIPTOR
    IOCTL_HID_GET_DEVICE_ATTRIBUTES
    IOCTL_HID_SET_FEATURE
    IOCTL_HID_READ_REPORT
    都是标准的HID请求命令,其中IOCTL_HID_SET_FEATURE 应该是用于告诉SPI总线驱动开启或者关闭触摸板功能的。
    最主要的就是 IOCTL_HID_READ_REPORT命令,这个就是获取触摸板数据。

    分析在 bootcamp版本是 6.1.6813,对应的触摸板SPI总线驱动的驱动日期是 2016/05/26, 版本 6.1.6500.0,
    (这里再次提到版本和日期,因为不同版本很可能是不同的数据格式,由于本人就一台mac机器,无法测试其他情况)

    数据格式结构大致如下:
    前46个字节是格式头,接着每个手指占据30个字节。
    比如 一个手指在触摸板上,IOCTL_HID_READ_REPORT获取到的数据长度是46 + 30 = 76个字节,
    如果是两个手指在触摸板,IOCTL_HID_READ_REPORT获取的长度是 46 + 30*2 = 106字节,以此类推。

    前46个字节描述成c语言数据结构大致如下:
    typedef unsigned char              u8;
    46 length
    struct tp_protocol
    {
        u8                  type;      // unknown type  =2
        u8                  clicked;   // 按住了触摸板, 不管几个按住,都是 1
        u8                  unknown1[5]; //
        u8                  is_finger;   // 触摸板有手指 1,当离开瞬间,出现 0
        u8                  unknown2[8]; //
        u8                  unknown3[8]; // 未知,固定 00-01-07-97-02-00-06-00
        u8                  finger_data_length; // 手指数据总长度, 手指个数*30
        u8                  unknown4[5]; //
        u8                  finger_number; //手指个数
        u8                  Clicked; // 同上边的clicked
        u8                  state;   // 手指在上边好像是 0x10, 手指离开瞬间最高设置 1,变成 0x80(0x90),最后离开后,还会出现 0x00
        u8                  state2;  // 手指在上边 0x20,离开瞬间 变 0
        u8                  state3;  // 平时0, Clicked为 0x10
        u8                  zero;    // 始终 0
        u8                  unknown5[10]; /
    };

    如上所示,其中unknown字段是 没能解析出来的,不过后来发现就已知的字段已经足够模拟鼠标动作了。

    手指的30个字节的c语言结构如下:
    / 30 length
    struct tp_finger
    {
        short             org_x; //按下后,这个数字不变,
        short             org_y; //
        short             x;     //随着手指移动改变,
        short             y;     //
        short            unknown[11];
    };

    其中unknown未知,org_x,org_y好像没啥用,最有用的是 x,y。表示的是手指在触摸板的坐标位置。
    整个触摸板(13寸机器)测试下来,触摸板范围,最左边大致是 -6300多, 左右边坐标 6800多,
    最上边坐标7700左右,最下边-200左右。这些都是用手指移动到边界得出来的大致数据。

    有了这些原始的触摸板的数据,我们基本就能确定能实现自己的mbp2017的触摸板驱动来模拟鼠标操作了。

    /
    再来看看windows平台下的HID驱动开发过程,
    HID(Human Interface Device,人机接口设备)是一类设备总称,用于提供人和电脑进行交互的接口设备,
    像最常用的鼠标,键盘等,触摸板,还有游戏使用的游戏杆等等。
    像鼠标键盘都是windows提供的标准驱动,我们在开发HID驱动时候,填写适当的HID描述符,
    告诉windows我们开发的是一个HID的鼠标或者HID键盘,windows自动就会给我们加载兼容的鼠标键盘驱动。
    同时我们在自己的HID驱动开发中正确响应 IOCTL_HID_XXX事件(主要是IOCTL_HID_READ_REPORT)
    这个鼠标键盘就能正常工作起来。看起来是非常简单明了的。确实也是如此。
    比如我们要虚拟鼠标键盘,也使用HID来开发,比起挂钩什么PS/2或者HOOK之类的做法,也显得简单和稳定得多,
    这个在以后开发的博客中会继续阐述。

    我们先看看WDM模型的HID开发过程,
    首先在 DriverEntry中注册
    IRP_MJ_INTERNAL_DEVICE_CONTROL, IRP_MJ_POWER, IRP_MJ_PNP 三个派遣函数,
    然后填写 HID_MINIDRIVER_REGISTRATION 结构的参数,调用HidRegisterMinidriver注册HID的小端口驱动,
    这样总体框架就建立起来了。
    然后在 IRP_MJ_POWER和IRP_MJ_PNP派遣函数中,按部就班的实现电源管理和即插即用事件,
    如果是真实HID设备,则要认真实现这两个功能,如果是虚拟设备,则不用太在意,找个现成框架套上去就行,
    接着就是核心处理事件 IRP_MJ_INTERNAL_DEVICE_CONTROL,
    在这个派遣函数中,一般处理
    IOCTL_HID_GET_DEVICE_DESCRIPTOR
    IOCTL_HID_GET_DEVICE_ATTRIBUTES
    IOCTL_HID_READ_REPORT
    三个事件就能让鼠标键盘跑起来,当然处理的越多,功能越完善,但是HID的IOCTL命令也多不多到哪去。
    是的,就是这么简单的框架,
    但是在这里,我们不打算使用WDM的框架,而使用的是 KMDF(WDF的内核部分,就是对WDM的封装 )来开发。

    KMDF就更加省事了,连 PNP和POWER也省略了,就只要关心
    IRP_MJ_INTERNAL_DEVICE_CONTROL 就可以了。
    但是正如微软自己所说,他们的WDF框架跟HID的minidriver小端口驱动在某些IO请求中存在冲突(IRP_MJ_POWER和IRP_MJ_PNP)
    因此WDF框架不能直接桥接 HID的class driver和mini driver,他们使用了一个折中方案,
    开发一个 mshidkmdf的驱动来桥接WDF框架和classdriver,在此驱动中调用
    HidRegisterMinidriver 来注册HID小端口驱动,
    并且mshidkmdf作为服务安装,而
    我们开发的HID驱动则作为LowerFilters来安装。详细可查看如下链接:
     https://msdn.microsoft.com/en-us/library/windows/hardware/ff540774
     这就是我们为何在上面的图中看到苹果的 AppleSPItrackpad触摸板驱动变成了 Lowerfilters 的底层过滤驱动了。

    最后看看我们开发KMDF模型的这款触摸板驱动的流程:
    首先在DriverEntry中,配置好参数,这里主要关心的是 EvtDeviceAdd 函数,
    调用WdfDriverCreate 来初始化框架。
    在EvtDeviceAdd 函数中,首先调用WdfFdoInitSetFilter 表明我们开发的是一个过滤驱动。
    然后调用WdfDeviceInitAssignWdmIrpPreprocessCallback 注册IRP_MN_QUERY_ID这个特殊查询事件,
    因为我们必须这么做,才能让windows识别到我们的驱动ID,
    对应这块触摸板,我们还得注册两个电源事件,就是D0状态(加电和掉电)转换,
    因为加电情况下,我们得通知SPI总线驱动,开启苹果的触摸板驱动,掉电情况做些停止操作处理,
    大致如下注册电源事件:
    //设置电源回调函数
        WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
        //设备处于工作(供电D0状态)或者非工作状态
        pnpPowerCallbacks.EvtDeviceD0Entry = EvtDeviceD0Entry ;// 设备加电时候被调用
        pnpPowerCallbacks.EvtDeviceD0Exit = EvtDeviceD0Exit;      // 设备掉电时候被调用
        WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); ///

    然后就是创建过滤设备,创建IO队列,一共两个队列,一个是默认的IO队列,用于处理 InternalDeviceControl请求,
    一个是手动队列,我们在处理 IOCTL_HID_READ_REPORT时候,需要入队等待处理,
    然后就是初始化一些相关变量等数据。
    这里我们读取SPI总线驱动的原始触摸板数据,使用的是串行读取,因此整个驱动创建一个全局的Request来重复使用,
    InternalDeviceControl 的IOCTL_HID_GET_DEVICE_DESCRIPTOR,IOCTL_HID_GET_DEVICE_ATTRIBUTES
    中我们把事先准备好的HID的鼠标描述符,属性等信息报告给classdriver, 
    然后接收到 IOCTL_HID_READ_REPORT命令时候调用WdfRequestForwardToIoQueue 加到手动的IO队列。

    EvtDeviceD0Entry函数被调用(就是设备加电了),发起全局的Request对SPI总线驱动的读取操作,
    同时设置这个Request的完成回调函数,在完成函数中分析处理读取到的触摸板数据,处理完成后接着继续发起对触摸板数据的读取。
     
    更详细的请查看稍后发布到GITHUB和CSDN上的源代码和驱动程序。

    驱动实现的功能一个5个(如上边所说)
    1,一个手指按住触摸板任意位置,另一个手指移动来达到拖动效果
    2,一个手指轻点或者一个手指按下触摸板,模拟鼠标左击
    3,两个手指轻点,或者一个手指按住触摸板右边3/4-4/4位置,模拟鼠标右击
    4,双指同时移动来模拟滚轮滚动
    5,三指拖移。

    如果你已经适应了苹果的触摸板windows的手势行为,则无需留意本文的内容。



    展开全文
  • PS/2鼠标履行一种双向同步串行协议。每次数据线上发送一位数据并且每在时钟线上发一个脉冲就被读入。鼠标可以发送数据到主机,而主机也可以发送数据到设备,但主机总是在总线上有优先权。它可以在任何时候抑制来自于...
  • 狼蛛诛命鼠标驱动是一款针对该型号鼠标打造的驱动程序,驱动安装即可对鼠标进行设置,鼠标...狼蛛诛命鼠标驱动介绍本款狼蛛诛命鼠标驱动是针对winxp\vista\win7\win8系统而开发的,有了此驱动用户可以更好,欢迎下载体验
  • 本资源包含 虚拟鼠标驱动程序源代码 及 最新驱动程序开发包WDK(WDK是WIN10平台下的SDK,如在其他平台,请下载相应的开发包)。安装SDK成功后,可在Visual Studio 2017下成功编译(Visual Studio 2012,Visual Studio ...
  • 宜博M628驱动是专为型号鼠标开发的一款鼠标驱动,虽然此款鼠标外观惊艳、性能强大,但是不安装驱动的话就无法使用了,通过该驱动用户可以对鼠标呼吸灯、宏命令、DPI进行设置,需要的朋友欢迎下载使用!功能介绍此款...
  • 牧马人2鼠标驱动是专为达尔优系列二代开发的一款鼠标驱动,不得不说该鼠标外观好看使用方便,但是必须安装该驱动才能正常使用,小编为大家提供了最新版的牧马人鼠标驱动,欢迎下载使用!使用说明下载完毕后解压缩,...
  • 雷柏V320鼠标驱动是专门针对雷柏V320游戏竞技鼠标最新开发的专用驱动软件。雷柏V320是一款双模鼠标,与赛睿无线大师非常类似,与无线是不同的是,雷柏V320搭配一条专用线和一个无线接收器,切换也相当方便,有很多...
  • 罗技G电竞鼠标驱动

    2019-04-09 01:18:18
    用于G系列电竞技鼠标的定义及设定,倒入模版数据,开发游戏操作等功能

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,017
精华内容 406
关键字:

鼠标驱动开发