usb鼠标驱动注解及测试

2013-10-12 13:03:44来源: dzsc

  鼠标驱动可分为几个部分:驱动加载部分、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 ", 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

  #include

  #include

  #include

  #include

  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 ");

  exit(0);

  }

  printf (" mouse opened, fd=%d ",fd);

  while(1)

  {

  printf("............................................... ");

  count=read(fd, ev_mouse, sizeof(struct input_event));

  for(i=0;i<(int)count/sizeof(struct input_event);i++)

  {

  printf("type=%d ",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 ",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 ",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);

  }

  }

  }

  close (fd);

  return 0;

  }

  运行结果如下:


  根据type、code、value的值,可以判断出鼠标的状态,具体值参考include/linux/input.h

关键字:usb鼠标  驱动注解  鼠标驱动

编辑:什么鱼 引用地址:http://www.eeworld.com.cn/Test_and_measurement/2013/1012/article_7940.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。
论坛活动 E手掌握
微信扫一扫加关注
论坛活动 E手掌握
芯片资讯 锐利解读
微信扫一扫加关注
芯片资讯 锐利解读
推荐阅读
全部
usb鼠标
驱动注解
鼠标驱动

小广播

独家专题更多

富士通铁电随机存储器FRAM主题展馆
富士通铁电随机存储器FRAM主题展馆
馆内包含了 纵览FRAM、独立FRAM存储器专区、FRAM内置LSI专区三大部分内容。 
走,跟Molex一起去看《中国电子消费品趋势》!
走,跟Molex一起去看《中国电子消费品趋势》!
 
带你走进LED王国——Microchip LED应用专题
带你走进LED王国——Microchip LED应用专题
 
电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2016 EEWORLD.com.cn, Inc. All rights reserved