usb驱动+输入子系统(转)

usb驱动程序的编写
从实用的角度出发,主要分为两个基本方面就行介绍,USB协议(和linux内核中的重要数据结构进行理解)和USB设备驱动程序的编写流程。不侧重于分析USB子系统
第一:
USB协议
1. USB驱动程序一般分为两种,一种是USB总线驱动程序,一种是USB设备驱动程序。
2. USB设备接入电脑以后,usb总线驱动程序会发出命令来获取设备的描述符,,USB设备必须返回描述符。
3.每一个USB设备接入PC时,USB总线驱动程序都会给他们分配一个编号,接在USB总线的上的每一个设备都有自己的编号,PC机访问某个USB设备时,发出的命令都含有对应的编号
4. USB设备接入PC后,PC要将分配的编号传递给设备,但是设备此时没有编号,那么只能使用默认的编号0
5.USB是主从结构,所有的USB传输,都是从USB主机发起的,USB设备没有“主动”通知主机的能力,靠的USB控制器不断的查询,如果发现有数据就去中断CPU
6.USB传输类型
(1)U盘:批量传输(可靠,没有时间保证)
(2)鼠标:中断传输(可靠,实时)
(3)摄像头:实时传输(不可靠,实时)
(4)读取描述符:USB设备的识别过程
7.USB传输的对象:端点,除了端点0以外,每个端点只支持一个方向的传输
   端点0用于控制传输。
   每一个端点都有类型(只有一个)和传输方向
8. 程序里的输入输出是针对主机来说的

app:  
-------------------------------------------
          USB设备驱动程序      // 知道数据含义
内核 --------------------------------------
          USB总线驱动程序      // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
-------------------------------------------
           USB主机控制器
           UHCI OHCI EHCI
硬件        -----------
              USB设备

USB总线驱动程序作用
三点:
(1)识别
(2)查找驱动匹配
(3)提供读写函数,不了解数据具体含义

USB传输的分类:
低速:1.5Mbps
全速:12Mbps
高速:480Mbps

USB总线驱动程序的过程,当USB设备接入时,硬件设备会产生中断
USB总线驱动程序的过程,当USB设备接入时,硬件设备会产生中断
hub_irq
   kick_khubd
        hub_thread
             hub_events
                   hub_port_connect_change
                            udev = usb_alloc_dev(hdev, hdev->bus, port1);//为刚加进来的设备分配并填充一个usb_device结构体,下面会将device加入到设备驱动模型中                                        
                            dev->dev.bus = &usb_bus_type;
                            choose_address(udev); //在1-128中找到没有使用的地址
                            hub_port_init  
                                 hub_set_address //把地址发送给设备
                                 usb_get_device_descriptor(udev, 8); // 获取8个字节的设备描述符(描述符一共有四类)
                                 retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);得到最大传输量后,再次获得完整设备描述符
                            usb_new_device(udev)  
                                  err = usb_get_configuration(udev); 得到各种描述符
                                  usb_parse_configuration            解析描述符得到各种信息
                                  device_add     //从usb中线中取出driver,用match进行匹配,如果匹配成功,调用driver的probe函数
                                                        注意,这里的match不是单纯比较名称,而是在driver中有一张表,里面包含了它所支持设备的信息,从这里我们可以看出,l
                                                        inux的USB驱动程序提供给我们的框架已经将设备模型中的总线和设备部分做好了,我们要实现的就是driver

USB子系统中重要的数据结构
第一:逻辑结构
1. 设备usb_device  代表一个具有符合功能的设备
2. 配置 usb_host_config  功能的组合
3. 接口 usb_interface 一种功能
struct usb_interface {
/* array of alternate settings for this interface,
  * stored in no particular order */
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting; /* the currently
      * active alternate setting */
unsigned num_altsetting; /* number of alternate settings */
/* If there is an interface association descriptor then it will list
  * the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
int minor;   /* minor number this interface is
      * bound to */
enum usb_interface_condition condition;  /* state of binding */
unsigned is_active:1;  /* the interface is not suspended */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned ep_devs_created:1; /* endpoint "devices" exist */
unsigned unregistering:1; /* unregistration is in progress */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned reset_running:1;
struct device dev;  /* interface specific device info */
struct device *usb_dev;
atomic_t pm_usage_cnt;  /* usage counter for autosuspend */
struct work_struct reset_ws; /* for resets in atomic context */
};
这里有一个重要的成员struct usb_host_interface *cur_altsetting;
struct usb_host_interface的意思是设置,一旦配置定下来以后,例如,铃声是一种配置,那么铃声的大小就是一种设置
struct usb_host_interface {
struct usb_interface_descriptor desc;
/* array of desc.bNumEndpoint endpoints associated with this
  * interface setting.  these will be in no particular order.
  */
struct usb_host_endpoint *endpoint;
char *string;  /* iInterface string, if present */
unsigned char *extra;   /* Extra descriptors */
int extralen;
};

这里存在着接口描述符和设备端点
4. 端点 usb _host_endpoint 单向的传输管道
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc;
struct list_head  urb_list;
void    *hcpriv;
struct ep_device   *ep_dev; /* For sysfs info */
struct usb_host_ss_ep_comp *ss_ep_comp; /* For SS devices */
unsigned char *extra;   /* Extra descriptors */
int extralen;
int enabled;
};

第二:描述符
1. 设备描述符struct usb_device_descriptor
2. 配置描述符struct usb_config_descriptor
3. 接口描述符struct usb_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))

以后就是用这里面的信息与驱动中idtable进行对比
4. 端点描述符
struct usb_endpoint_descriptor {
__u8  bLength;                            //端点描述符长度,B为单位
__u8  bDescriptorType;
__u8  bEndpointAddress;         //这个成员包含很多信息
__u8  bmAttributes;                 //端点类型,四种传输类型
__le16 wMaxPacketSize;
__u8  bInterval;                       //主机对设备的查询时间
/* NOTE:  these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8  bRefresh;
__u8  bSynchAddress;
} __attribute__ ((packed));
注意:0号端点是特殊的端点,它没有自己的端点描述符
bEndpointAddress:包含端点方向(最高位,使用USB_ENDPOINT_DIR_MASK,它的含义就是0x80,判断端点方向),端点地址,端点号码(0-3位,与0xff进行与操作)

总结:这8个数据结构的基本关系是
通过usb_device 可以找到 usb_host_config和struct usb_device_descriptor
通过usb_host_config可以找到usb_interface和truct usb_config_descriptor
通过usb_interface可以找到usb_host_interface
通过usb_host_interface可以找到truct usb_interface_descriptor 和 usb _host_endpoint
通过 usb _host_endpoint可以找到usb_endpoint_descriptor


第二:
具体USB鼠标驱动

1. 设备模型
USB设备驱动程序的编写要遵循设备驱动总线的模型,linux系统已经提供好了总线和设备,我们要实现的就是驱动的编写了。
所以,顺利成章,上来先要分配一个usb_driver结构体,将这个结构体注册到设备模型
static struct usb_driver usbmouse_as_key_driver = {
.name  = "usbmouse_as_key_",
.probe  = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};

static int usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
这几句话我我想正常人都能写的出来,呵呵
接下来实现简单的probe和disconnect函数
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
printk("found usbmouse!\n");
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
printk("disconnect usbmouse!\n");
}

比较肯定要提供那张表格啊
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
  USB_INTERFACE_PROTOCOL_MOUSE) },

{ }
};
到这里要解释一下
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;
};
这就是这张表格具备的信息
#define USB_INTERFACE_INFO(cl, sc, pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
.bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), \
.bInterfaceProtocol = (pr)
所以刚才的定义等价于
static struct usb_device_id usbmouse_as_key_id_table [] = {
    {
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO,
.bInterfaceClass = USB_INTERFACE_CLASS_HID,
.bInterfaceSubClass = USB_INTERFACE_SUBCLASS_BOOT,
.bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE,
        
     }
{}//表示结束
}
满足这些个条件,那么这个driver就支持这个设备,从而调用probe函数
还有其他用来定义这个表格的宏
#define USB_DEVICE(vend,prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
.idVendor = (vend), \
.idProduct = (prod)
#define USB_DEVICE_VER(vend, prod, lo, hi) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \
.idVendor = (vend), \
.idProduct = (prod), \
.bcdDevice_lo = (lo), \
.bcdDevice_hi = (hi)
match_flags的作用:因为struct usb_device_id中有很多匹配项,match_flags的作用就是告诉内核需要比较哪些项

2. input子系统
接下来,因为鼠标是个输入设备,自然要用到input子系统了,input子系统三要素,分配,设置,注册,上报。其中的分配和设置那没有悬念的要在probe函数中完成了
下面看probe函数
static int usbmouse_as_key_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;
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;

/* a. 分配一个input_dev */
uk_dev = input_allocate_device();

/* b. 设置 */
/* b.1 能产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);

/* b.2 能产生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);

/* c. 注册 */
input_register_device(uk_dev);

3. USB传输问题
接下来的要讨论的就是USB设备驱动程序的重点问题了我,究竟怎么将数据通过USB总线进行传输呢,这里是要必须知道的就是三个
基本要素好一个核心数据结构,就是传说中的struct urb,要进行USB传输,必须分配并填充一个URB结构,然后提交到内核,在恰
当的时候调用注册好的函数就是数据的处理
这里的三个基本要素就是源址,长度,目的
源址:
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
这里引出一个新的概念,就是管道,pipe,它是传输数据的虚拟载体。在linux中用unsigned int表示。在linux中一条管道只能进行一个方向上的传输,所以USB通信中,一把呢需要两条管
道,一个用于接收数据,一个用于发送数据。
这样,linux中提供宏来创建管道

/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))                       //创建发送控制管道
#define usb_rcvctrlpipe(dev,endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)          //创建接收控制管道
#define usb_sndisocpipe(dev,endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))                  
#define usb_rcvisocpipe(dev,endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)    
#define usb_sndbulkpipe(dev,endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint))                          //创建发送批量管道
#define usb_rcvbulkpipe(dev,endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)             //创建发送批量管道
#define usb_sndintpipe(dev,endpoint) \                                          
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))                     //创建发送中断管道
#define usb_rcvintpipe(dev,endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)        //创建接收中断管道

注意:这里的发送和接收都都是针对USB主机的,管道和端点一一对应
上面可以看出,由于pipe是一个unsigned int的数,调用__create_pipe(dev, endpoint)创建管道其实就是利用设备号和端点号填充pipe中的位
static inline unsigned int __create_pipe(struct usb_device *dev,
  unsigned int endpoint)
{
return (dev->devnum << 8) | (endpoint << 15);
}

由此可见管道的8-14位为设备号,15-18位为端点号,7位为管道方向,30,,31为管道类型
长度
len = endpoint->wMaxPacketSize;//代表端点一次传输的最大数据量,字节表示,大于这个最大量最分批次传输
目的
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
将三要素完成后,下面就开始进行urb的相关工作了,老样子,先申请urb,接着填充,最后提交
uk_urb = usb_alloc_urb(0, GFP_KERNEL);

usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

usb_submit_urb(uk_urb, GFP_KERNEL);

return 0;
}
4. 注册函数进行数据处理
我们看到在填充urb时候,制定了一个函数,那就是usbmouse_as_key_irq,当有数据产生的时候,这个函数就会唤醒进程,实际上这里利用了一个完成量的实现
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
/* USB鼠标数据含义
  * data[0]: bit0-左键, 1-按下, 0-松开
  *          bit1-右键, 1-按下, 0-松开
  *          bit2-中键, 1-按下, 0-松开
  *
     */
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
  /* 左键发生了变化 */
  input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
  input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
  /* 右键发生了变化 */
  input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
  input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
  /* 中键发生了变化 */
  input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
  input_sync(uk_dev);
}

pre_val = usb_buf[0];
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}

这里主要进行数据处理,提交事件和重新提交urb
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值