Chinaunix首页 | 论坛 | 博客
  • 博客访问: 51364
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-09 22:49
文章分类
文章存档

2015年(9)

2014年(8)

我的朋友

分类: LINUX

2015-01-30 16:27:03

原文地址:linux usb 鼠标(1) 作者:steven_miao

usb鼠标
 
谨以此文纪念过往的岁月。
 
一.前言
众所周知,usb子系统是个庞然大物,这个里面涉及的东西很多,网上也有很好的资料,如复旦ABC的,堪称经典。不过这儿仅以此文来记录学习的历程,不能和那些大牛比啊,各位不要见笑。
二.usb设备描述符
usb子系统很大,从哪儿下手呢。以前学习过usb hub的注册,现在主要看一个设备插入hub后的种种特征。
在这个之前需要理解部分usb协议,关于usb的电气等等都不管,只关注usb驱动软件部分。
1.usb描述符
标准usb设备有四层描述符。这个也许很突兀,但是仍想先介绍一下。关于其具体使用,下面在讲驱动具体实现时再说。
下面的四个描述符,是有包含关系的,设备描述符包括一个或多个配置描述符,配置描述符包括一个或多个接口描述符,而接口描述符包括一个或多个端点描述符。
对于下面的描述符均采用linux中的具体实现的结构体来说明。
1.1 Device Descriptor
struct usb_device_descriptor {
 __u8  bLength;              --描述符长度
 __u8  bDescriptorType;      --描述符类型:设备描述符0x01
 __le16 bcdUSB;              --usb规范版本号
 __u8  bDeviceClass;         --类代码
 __u8  bDeviceSubClass;      --子类代码
 __u8  bDeviceProtocol;      --协议代码
 __u8  bMaxPacketSize0;      --端点0支持最大数
 __le16 idVendor;            --供应商ID
 __le16 idProduct;           --产品ID
 __le16 bcdDevice;           --设备版本号
 __u8  iManufacturer;        --供应商字符串描述符的索引值
 __u8  iProduct;             --产品字符串描述符的索引值
 __u8  iSerialNumber;        --设备序列号
 __u8  bNumConfigurations;   --所支持的配置数
} __attribute__ ((packed));   --结构体字符类型对齐
1.2 Configuration Descriptor
struct usb_config_descriptor {
 __u8  bLength;              --描述符长度
 __u8  bDescriptorType;      --描述符类型
 __le16 wTotalLength;        --配置信息的总长度
 __u8  bNumInterfaces;       --所支持的接口数
 __u8  bConfigurationValue;  --配置值
 __u8  iConfiguration;       --字符串描述符的索引值
 __u8  bmAttributes;         --配置特征
 __u8  bMaxPower;            --所需最大的总线电流
} __attribute__ ((packed));
1.3 Interface Descriptor
struct usb_interface_descriptor {
 __u8  bLength;
 __u8  bDescriptorType;
 __u8  bInterfaceNumber;     --接口编号
 __u8  bAlternateSetting;    --备用接口标号
 __u8  bNumEndpoints;        --接口数目
 __u8  bInterfaceClass;      --接口类型
 __u8  bInterfaceSubClass;   --接口子类型
 __u8  bInterfaceProtocol;   --接口所用协议
 __u8  iInterface;           --接口索引字符串数值
} __attribute__ ((packed));
1.4 Endpoint Descriptor
struct usb_endpoint_descriptor {
 __u8  bLength;
 __u8  bDescriptorType;
 __u8  bEndpointAddress;      --端点号包括传输方向
 __u8  bmAttributes;          --端点属性
 __le16 wMaxPacketSize;       --最大数据包长度
 __u8  bInterval;             --访问间隔
 
 __u8  bRefresh;             
 __u8  bSynchAddress;
} __attribute__ ((packed));
1.5 usb 控制传输
struct usb_ctrlrequest {
 __u8 bRequestType;           --指明控制请求的特征
 __u8 bRequest;               --指明控制请求的请求号
 __le16 wValue;               --控制请求的参数
 __le16 wIndex;               --控制请求的参数,主要是传输索引值或偏移量
 __le16 wLength;              --控制事务数据阶段需传输的字节数
} __attribute__ ((packed));
上面的设备描述符主要在设备加载进系统时有用,因为系统需要根据这些设备描述符来匹配不同驱动。在usb子系统中usb设备的mattch规则则是根据设备的usb_device_id来匹配。
三.usb驱动
对于usb设备驱动而言比较重要的是在设备驱动加载时
1. usb_register(struct usb_driver *)
2. usb_unregister(struct usb_driver *)
而在usb_driver中需要初始化成员的一般是
static struct usb_driver usb_xxxx_driver = {
 .name =  "xxxx",
 .probe = usb_xxxx_probe,
 .disconnect = usb_xxxx_disconnect,
 .id_table = usb_xxxx_id_table,
};
在usb_ xxxx _id_table中是该驱动支持的设备ID。其初始化格式一般为(以usb键盘为例)
static struct usb_device_id usb_kbd_id_table [] = {
 { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
  USB_INTERFACE_PROTOCOL_KEYBOARD) },
 { }     
};
在usb_ xxxx _probe函数中一般会申请一个urb 采用usb_alloc_urb(int iso_pockets, GFP_KERNEL)其中第一个参数用于表示该urb包含多少个等时数据包,如果不用等时urb,则设为0.在创建一个urb后,需要调用以下4个函数中一个来正确初始化一个urb
1.usb_fill_int_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,void *transter_buffer,
                   int buffer_length,usb_complete_t compete,void *context,int interval)
Pipe 该urb所发送的目标usb设备的特定端点,采用usb_sndintpipe或usb_rcvintpipe创建
Compete 该urb结束后所调用的函数
Interval 该urb应该被调度的间隔
2. usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,
                      void *transter_buffer Int buffer_length,usb_complete_t compete,void *context)
Pipe 该urb所发送的目标usb设备的特定端点,采用usb_sndbulkpipe或usb_rcvbulkpipe创建
3.usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_pocket,
                        void *transter_buffer, Int buffer_length,usb_complete_t compete,void *context)
setup_pocket 指向即将被发送端点的设置数据包的数据
Pipe 该urb所发送的目标usb设备的特定端点,采用usb_sndctlpipe或usb_rcvctlpipe创建
4. 等时urb需要自己手工初始化
5. 在创建和初始化urb完成后需要提交urb
usb_subnit_urb(struct urb *urb,int mem_floags)
如果就简单的usb驱动可以说就可以了,注意在usb_ xxxx _disconnect中将所申请以及开辟的空间要及时释放。而usb通过驱动获取到数据后会通过其他的系统将数据传输到应用层,其中如usb鼠标及usb键盘会通过input子系统传输到应用层。
如果是普通的usb设备会通过cdev系统传输,可以直接通过usb_register_dev简单调用就可以了。
四.usb 鼠标驱动
说什么都是虚的,还是来看源码吧。以usb mouse为例,来看usb设备的驱动。
在usb插入hub时,usb core会为设备创建usb device,而我们仅仅需要的是将usb设备的驱动添加入usb系统。如何去注册一个usb driver呢,与以前的设备驱动模型是
一样的,调用usb_register来注册设备接口驱动。
4.1 usb 驱动注册
其函数原型如下,猛然一看,感觉像是注册一个usb设备。而这函数说白了就是usb_register_driver的马甲,还是调用usb_register_driver。
static inline int usb_register(struct usb_driver *driver)
{
 return usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME);
}
那看usb_register_driver是如何实现的。
记住这个函数并不是注册整个设备的驱动,而是设备接口的驱动。这个很好去理解,例如usb扬声器,其就需要两个驱动,一个是usb键盘的按键一个是usb音频流。那在这种情况下一个设备就需要两个usb驱动,一个是usb键盘驱动一个是usb音频流驱动。其中usb_register_driver是为其中的一个接口提供驱动。
struct usbdrv_wrap {
 struct device_driver driver;  --设备驱动
 int for_devices;              --如果非零时设备驱动,而零则代表是接口驱动。
};
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,const char *mod_name)
{
 int retval = 0;
 if (usb_disabled())
  return -ENODEV;
 new_driver->drvwrap.for_devices = 0;
 new_driver->drvwrap.driver.name = (char *) new_driver->name;
 new_driver->drvwrap.driver.bus = &usb_bus_type;
 new_driver->drvwrap.driver.probe = usb_probe_interface;     --在usb系统中很多的任务都由usb core完成,想这种interface探测驱动都是usb core完成的。
 new_driver->drvwrap.driver.remove = usb_unbind_interface;
 new_driver->drvwrap.driver.owner = owner;
 new_driver->drvwrap.driver.mod_name = mod_name;             --上面的一段就是对标准driver的初始化。
 spin_lock_init(&new_driver->dynids.lock);
 INIT_LIST_HEAD(&new_driver->dynids.list);
 retval = driver_register(&new_driver->drvwrap.driver);     --注册驱动。
 if (!retval) {
  pr_info("%s: registered new interface driver %s\n",usbcore_name, new_driver->name);
  usbfs_update_special();
  usb_create_newid_file(new_driver);
 } else {
  printk(KERN_ERR "%s: error %d registering interface "" driver %s\n",usbcore_name, retval, new_driver->name);
 }
 return retval;
}
蓦然回首,你会发现所有的一切一切都是浮云,只有driver_register才是真的,几乎所有的驱动最终都会调用该函数来注册驱动,说白了,几乎所有的驱动模型都是该函数的一种分支,在其基础上进行适合于各种设备的驱动模型。
所谓的usb_register完了。那看一下其传入的参数,就是usb_driver结构。
struct usb_driver {
 const char *name;
 int (*probe) (struct usb_interface *intf,const struct usb_device_id *id);
 void (*disconnect) (struct usb_interface *intf);
 int (*ioctl) (struct usb_interface *intf, unsigned int code,void *buf);
 int (*suspend) (struct usb_interface *intf, pm_message_t message);
 int (*resume) (struct usb_interface *intf);
 int (*reset_resume)(struct usb_interface *intf);
 int (*pre_reset)(struct usb_interface *intf);
 int (*post_reset)(struct usb_interface *intf);
 const struct usb_device_id *id_table;
 
 struct usb_dynids dynids;
 struct usbdrv_wrap drvwrap;
 unsigned int no_dynamic_id:1;
 unsigned int supports_autosuspend:1;
 unsigned int soft_unbind:1;
};
这个结构体是就是一些函数指针的结合体,几乎所有的driver都是这个鸟样。id_table是用去区别各种驱动的识别码,也是device查询适合的驱动和驱动查询适合的device的标志。
那来看这个结构体
struct usb_device_id {
 /* which fields to match against? */
 __u16  match_flags;   --说明根据下面的那些区域来进行匹配
 
 /* Used for product specific matches; range is inclusive */
 __u16  idVendor;
 __u16  idProduct;
 __u16  bcdDevice_lo;
 __u16  bcdDevice_hi;
 /* Used for device class matches */
 __u8  bDeviceClass;
 __u8  bDeviceSubClass;
 __u8  bDeviceProtocol;
 /* Used for interface class matches */
 __u8  bInterfaceClass;
 __u8  bInterfaceSubClass;
 __u8  bInterfaceProtocol;
 /* not matched against */
 kernel_ulong_t driver_info;
};
看英文注释就可以理解了,其实该结构体提供了不同的匹配规则。如根据设备制造厂商,设备类,接口类来进行匹配。
以usb mouse为例:
static struct usb_device_id usb_mouse_id_table [] = {
 { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
  USB_INTERFACE_PROTOCOL_MOUSE) },
 { } 
};
那这个就需要讲述一下各个宏定义的含义了。
宏定义USB_INTERFACE_INFO用于描述根据接口类来match的规则。
#define USB_INTERFACE_INFO(cl, sc, pr) \
 .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \  
 .bInterfaceClass = (cl), \
 .bInterfaceSubClass = (sc), \
 .bInterfaceProtocol = (pr)
而宏定义USB_DEVICE_ID_MATCH_INT_INFO说明match的flags,如下面宏定义说明为根据接口的类,子类以及协议来匹配。
#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)
match_flags一般有下面五种。
USB_DEVICE_ID_MATCH_INT_INFO    --根据接口信息
USB_DEVICE_ID_MATCH_DEV_INFO    --根据设备的信息
USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION --根据设备制造信息和版本
USB_DEVICE_ID_MATCH_DEV_RANGE       --根据设备版本
USB_DEVICE_ID_MATCH_DEVICE          --根据设备制造信息
而宏定义usb_device_id的方法根据上述五种区别以及部分组合有六种方法进行赋值。这个可以具体参考/drivers/usb/core/usb.h。
4.2 usb mouse探测函数
何所谓probe,都应该知道的。主要来看probe中是如何操作的。
在usb core调用驱动probe时,usb core已经将intf和驱动捆绑在一起,会将intf的信息同时传入,那这里就来看怎么使用的。其实这个会根据不同的设备其probe函数是不同的这里仅仅是为了更好的理解usb驱动才来看看的,这里将关于input子系统的程序删除。
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
 struct usb_device *udev = interface_to_usbdev(intf);   --根据intf获取usb_device信息。
 struct usb_host_interface *interface;                  --这个会指向当前的设置
 struct usb_endpoint_descriptor *endpoint;              --端点描述符
 struct usb_mouse *umouse;
 struct input_dev *input_dev;
 int pipe,maxp;
 interface = intf->cur_altsetting;
 if(interface->desc.bNumEndpoints != 1)                 --usb mouse的端点应该只有两个一个是0端点专门用于控制,而另一个则是中断端点。
  return -ENODEV;
 endpoint = &(interface->endpoint[0].desc);
 if(!usb_endpoint_is_int_in(endpoint))                  --判断端点是否为中断in类型。
  return -ENODEV;
 pipe = usb_rcvintpipe(udev,endpoint->bEndpointAddress); --创建一个接受中断类型pipe
 maxp = usb_maxpacket(udev,pipe,usb_pipeout(pipe));      --usb最大包数据数
 umouse->data = usb_buffer_alloc(udev,8,GFP_ATOMIC,&(umouse->data_dma));  --分配DMA空间
 umouse->irq = usb_alloc_urb(0,GFP_ATOMIC);   --申请一个urb
 
 usb_fill_int_urb(umouse->irq,udev,pipe,umouse->data,(maxp > 8 ? 8 : maxp),usb_mouse_irq,umouse,endpoint->bInterval); --填充urb
 umouse->irq->transfer_dma = umouse->data_dma;             --传输dma的地址
 umouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;   --传输标志符
 usb_set_intfdata(intf, umouse);                           --设置私有数据
}
 
 
阅读(926) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~