学习笔记 --- LINUX USB设备驱动的编写

上一篇文章说过了USB总线驱动框架,在USB总线驱动里面,当设备插上USB HOST时,产生中断,唤醒hub_thread线程,这个线程会给设备分配一个地址,获取设备描述符,最后添加设备(device_add),实现了总线框架的设备端的添加,还有驱动端要实现,这里我们以一个简单的实例来说明如何实现一个USB设备驱动。

/*
 * drivers\hid\usbhid\usbmouse.c
 */
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
static struct usb_device_id usb_mouse_id_table [] = {
 { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
  USB_INTERFACE_PROTOCOL_MOUSE) },
 { } /* Terminating entry */
};
/* 匹配时会调用这个函数 */
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
     struct usb_device *dev = interface_to_usbdev(intf);

 printk("found usbmouse!\n");

 printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
 printk("VID    = 0x%x\n", dev->descriptor.idVendor);
 printk("PID    = 0x%x\n", dev->descriptor.idProduct);
 
 return 0;
}
/* 拔出时会自动调用这个函数 */
static void usb_mouse_disconnect(struct usb_interface *intf)
{
    printk("disconnect usbmouse!\n");
}


static struct usb_driver usb_mouse_driver = {
 .name  = "usbmouse",
 .probe  = usb_mouse_probe,
 .disconnect = usb_mouse_disconnect,
 .id_table = usb_mouse_id_table,
};

static int __init usb_mouse_init(void)
{
 int retval = usb_register(&usb_mouse_driver);//注册

 return retval;
}

static void __exit usb_mouse_exit(void)
{
 usb_deregister(&usb_mouse_driver);//卸载
}

module_init(usb_mouse_init);
module_exit(usb_mouse_exit);
MODULE_LICENSE("GPL");

这是一个最简单的USB设备驱动框架,从这里可以看出,USB设备驱动端的添加是通过usb_register来实现的:

usb_register ---> usb_register_driver ---> driver_register

如果插上USB鼠标,会打印出一些该USB设备描述符的信息。

这里以USB鼠标为例来说明。

测试:
1. make menuconfig去掉原来的USB鼠标驱动
-> Device Drivers 
  -> HID Devices
  <> USB Human Interface Device (full HID) support 
2. make uImage 并使用新的内核启动
3. insmod usbmouse_as_key.ko
4. 在开发板上接入、拔出USB鼠标
5. 打印出如下信息:
# usb 1-1: new low speed USB device using s3c2410-ohci and address 2
usb 1-1: configuration #1 chosen from 1 choice
found usbmouse!
bcdUSB = 110
VID    = 0x93a
PID    = 0x2510
usb 1-1: USB disconnect, address 2
disconnect usbmouse!
上面程序的功能只是注册了一个USB设备驱动,但是还没有实现这个USB设备的功能,设备到底要干些什么事,下面完善这个程序,实现一个USB 鼠标的左键代表l,右键代表s,滚轮键代表enter键:

/*
 * drivers\hid\usbhid\usbmouse.c
 */
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;

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) },
 //{USB_DEVICE(0x1234,0x5678)},
 { } /* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
 static unsigned char pre_val;
#if 0   //调试时打印USB传输的数据
 int i;
 static int cnt = 0;
 printk("data cnt %d: ", ++cnt);
 for (i = 0; i < len; i++)
 {
  printk("%02x ", usb_buf[i]);
 }
 printk("\n");
#endif
 /* USB鼠标数据含义
  * data[0]: bit0-左键, 1-按下, 0-松开
  *          bit1-右键, 1-按下, 0-松开
  *          bit2-中键, 1-按下, 0-松开 
  *
     */
 if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))//设备端点会将设备的数据写入驱动缓冲区usb_buf
 {
  /* 左键发生了变化 */
  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);
}

/* 匹配时调用这个函数 */
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
 struct usb_device *dev = interface_to_usbdev(intf);//获取usb接口结构体中的usb设备结构体
 struct usb_host_interface *interface;                      
 struct usb_endpoint_descriptor *endpoint;
 int pipe;
 
 interface = intf->cur_altsetting;  //获取usb接口结构体中的usb host接口结构体
 endpoint = &interface->endpoint[0].desc;//获取usb host接口结构体中的端点描述结构体

 /* 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);//能产生L类事件
 set_bit(KEY_S, uk_dev->keybit);//能产生S类事件
 set_bit(KEY_ENTER, uk_dev->keybit);//能产生ENTER
 
 /* c. 注册 */
 input_register_device(uk_dev);
 
        /* 在向下分析之前我们有必要先补充一些关于urb的知识,请看注释 */

 /* d. 硬件相关操作 */
 /* 数据传输3要素: 源,目的,长度 */
 /* 源: USB设备的某个端点 */
 pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//创建管道,用于连接驱动程序缓冲区和设备端口

 /* 长度: */
 len = endpoint->wMaxPacketSize;

 /* 目的: */
 usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);//这就是驱动程序缓冲区了,缓冲区物理地                                                                                                                                                  //址在usb_buf_phys中

 /* 使用"3要素" */
 /* 分配usb request block */
 uk_urb = usb_alloc_urb(0, GFP_KERNEL);
 /* 使用"3要素设置urb" */
 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;

 /* 使用URB */
 usb_submit_urb(uk_urb, GFP_KERNEL);//提交urb给usb主控制器驱动
 
 return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
 struct usb_device *dev = interface_to_usbdev(intf);

 //printk("disconnect usbmouse!\n");
 usb_kill_urb(uk_urb);
 usb_free_urb(uk_urb);

 usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
 input_unregister_device(uk_dev);
 input_free_device(uk_dev);
}

/* 1. 分配/设置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");

注释: 

1、定义:USB请求块(USB request block,urb)在USB设备驱动程序中用来描述与USB设备通信所用的基本载体和核心数据结构。非常类似于网络设备驱动程序中的sk_buff结构体,是USB主机与设备通信的电波。
2、URB处理流程:
(1)USB设备驱动程序创建并初始化一个访问特定USB设备特定端点的urb,并提交给USB core
(2)USR core提交该urb到到USB主控制器驱动程序。
(3)USB主控制器驱动程序根据该urb描述的信息,来访问usb设备
(4)当设备访问结束后,USB主的控制器驱动程序通知USB设备驱动程序。
3、urb相关函数
(1)创建urb的函数:
原型:struct urb * usb_alloc_urb(int iso_packets, gfp_t mem_flags)
参数:iso_packets:urb所包含的等时数据包的个数
           mem_flags  :内存分配标志(如GFP_KERNEL)
(2)初始化urb(以中断urb为例):
原型: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_fn,
         void *context,
         int interval)
参数: urb                      :要初始化的urb指针
dev                      :要访问的设备
pipe                     :要访问的端点所对应的管道,使用usb_andintpipe()或usb_rcvintpipe()创建(管道:驱动程序的数据缓冲区和一个端点的连接,它代表了一种在两者之间移动数据的能力) 
transfer_buffer     :要传输的数据的缓冲区。
buffer_length       :transfer_buffer所指缓冲区的长度
complete_fn        :当完成urb所请求的操作时,要调用的回调函数
context                :complet_fn函数所需的上下文,通常取值为dev
interval                 :urb被调度的时间间隔 
(3)提交urb:在完成urb的创建和初始化后,urb便可以通过usb_submit_urb函数来提交给USB核心:
原型:int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
参数:urb              :指向urb的指针
      mem_flags :内存分配标志,它用于告诉USB核心如何分配内存缓冲区

urb被提交到USB核心后,usb核心指定usb主控制器驱动程序来处理该urb,在下面情况下,urb会被认为处理完成:
(1)urb被成功发送给设备,并且设备返回正确的确认。如果urb->status为0,意味着对于一个输出urb,数据被成功发送,对于一个输入urb,数据被成功接收
(2)如果发送数据或接收数据发生了错误,urb->status会记录错误值
(3)urb被“取消”,这发生在驱动通过usb_unlink_urb()或usb_kill_urb()函数取消urb,或ur虽已提交,而usb设备被拔出的情况下。urb完成,函数将被调用

总结:当插入usb鼠标时,会根据驱动程序注册的id.table来遍历驱动并判断驱动与设备是否匹配。一旦匹配就会调用probe函数,所以我们的主要工作放在了probe函数里面。在probe函数中我们首先获得usb设备端口信息,这些信息在设置urb的时候会用到。接着设置能产生哪类事件以及这类事件里的哪些事件。然后就是重点了,我们来设置urb请求块,可以通过设置它来控制驱动程序与设备端点之间的通信。设置并提交之后,USB主控制器就会根据它以一定的间隔去查询usb设备是否发送数据到usb驱动程序缓冲区,一旦发现了usb设备发送数据过来就会向cpu发出中断请求,并执行相应的中断函数。在中断函数里面,我们根据usb设备发送过来的数据,判断发生了什么事情,然后上报相应的事件,最后不要忘记重新提交urb。


测试:
1. insmod usbmouse_as_key.ko
2. ls /dev/event*
3. 接上USB鼠标
4. ls /dev/event*
5. cat /dev/tty1    然后按鼠标键

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值