liunx驱动----USB驱动
现象:把usb设备接入电脑
1.Windows发现设备
2.跳出一个对话框提示安装驱动程序
问1:既然没有驱动程序,为什么了够知道是什么驱动了??
答1:Windows里面已经有了usb总线驱动程序,接入usb设备后,是“总线驱动程序知道”是什么驱动。提示安装设备驱动程序
usb总线驱动程序负责识别USB设备,给usb设备找到对应的驱动程序
问2.usb设备种类多,为什么接入电脑就能够识别出来了?
答2.PC和USB设备都的遵守一些规范。
比如:USB接入电脑后PC机会发出,读取设备类型的命令(描述符)。然后USB设备就回答给PC机(描述符)。
问3.PC机上接有非常多的USB设备,怎么分辨?
答3.接在USB总线上的每个USB设备都有自己的编号(地址)PC机想访问某个USB设备的时候,发出的信息都有对方的编号(地址)
问4.USB设备刚接入PC的时候还没有编号,那么PC怎么把分配的编号告诉
答4.新接入的USB设备的默认编号为0,在没有分配新的编号前。PC使用0编号和USB通讯。
问5.为什么一接入USB设备,PC机就能发现USB设备
答5.PC的USB口内部。D-和D+接了下拉电阻,没有接入USB的时候为低电平,USB设备的USB口内部,D-或D+接了上啦电阻,接入时就有一个硬件的信号通知PC机有USB设备接入。
其它概念:
1.USB是主从设备(主从结构)
所有的USB传输都是从USB主机发起的。USB设备没有主动通知USB主机的能力。
例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动的等待PC机来读取数据
2.USB的传输类型
a.控制传输:可靠,时间有保证,比如USB设备的识别过程
b.批量传输:可靠,时间没有保证,U盘
c.中断传输:可靠,实时,USB鼠标
d.实时传输:不可靠,实时,USB摄像头
3.USB传输的对象:端点(endpoint)
读写U盘,可以细化为:把数据写到u盘的端点1,从u盘的端点2读出数据
除了端点0以外,每个端点只支持一个方向的数据传输。
端点0用于控制传输,既能输出也能输入
4.每一个端点都有传输类型,传输方向。
5.术语里,程序里说的输入(IN)输出(OUT)。都是基于USB主机的立场说的。
比如说对于鼠标,数据是从鼠标传输到PC机的。对应的端点称为“输出端点”
USB驱动
设备驱动: 1.知道数据的含义。
USB总线驱动程序(的作用)----> 1.识别设备
2.找到并安装对应的USB设备驱动
3.提供USB读写函数
硬件: USB主机控制器
USB驱动程框架:
APP:
---------------------------------------------
USB设备驱动程序
内核--------------------------------------
USB总线驱动程序
---------------------------------------------
USB主机控制器
硬件 ---------------------
USB设备
/*****************************USB总线驱动程序****************************************/
USB设备驱动程序编写:
1.分配/设置usb_derver结构体
.id_table 表示支持哪些usb设备
.probe
.disconnect
2.注册
测试:
1.make munconfig 去掉内核中原来的usb鼠标驱动
-> Device Drivers
-> HID Devices
< > USB Human Interface Device (full HID) support
2.make uImage 并使用新的内核启动
使用新内核启动:nfs 30000000 192.168.1.102:/home/book/work/nfs_root/first_fs/uImage_nohid; 30000000
挂接根文件系统:mount -t nfs -o nolock,vers=2 192.168.1.102:/home/book/work/nfs_root/first_fs /mut
3.insmod usbmonus_as_key.ko
实验1:
目的:通过控制台将数据数据移动按下数据打印出来。
步骤1:得到 usb_host_interface 结构体
interface = intf->cur_altsetting;//interface结构体
步骤2:获取端点描述符
endpoint = &interface->endpoint[0].desc;//端点描述符
步骤3://1.分配一个input_dev 结构体
uk_dev = input_allocate_device();
步骤4:设置uk_dev结构体
步骤5:注册input_dev结构体
步骤6:硬件相关的操作 (使用usb总线设备驱动来收发数据)
//数据传输的三要素: 源 、目的、长度
//源:USB的某个端点
代码分为:
usbmouse_as_key_probe函数(初始化需要操作鼠标的设置)
usbmouse_as_key_irq函数(当鼠标有数据变化时将调用这个函数)
usbmouse_as_key_disconnect函数(鼠标的断开处理函数)
usbmouse_as_key_probe代码如下:
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id) { /*打印厂家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;//interface结构体 endpoint = &interface->endpoint[0].desc;//端点描述符 #if 0 struct usb_device *dev = interface_to_usbdev(intf);//通过 interface_to_usbdev接口函数得到 usb_device id printk("bcd_USB = %x\n",dev->descriptor.bcdUSB); printk("vid = 0x%x\n",dev->descriptor.idVendor); printk("pid = 0x%x\n",dev->descriptor.idProduct); printk("found usbmonuse!\n"); #endif //1.分配一个input_dev 结构体 uk_dev = input_allocate_device(); //2.设置分配的这个结构体 //2.1能产生那类事件 set_bit(EV_KEY, uk_dev->evbit);//设置能够产生按键类事件uk_dev->evbit是表示能够产生那类事件 set_bit(EV_REP, uk_dev->evbit);//设置能够产生重复类事件uk_dev->evbit是表示能够产生那类事件 //2.2能产生哪些事件 set_bit(KEY_L,uk_dev->keybit);//通过设置uk_dev->keybit 实现产生一个KEY_L的事件 (相当于按键按下 L 键) set_bit(KEY_S,uk_dev->keybit);//通过设置uk_dev->keybit 实现产生一个KEY_S的事件 (相当于按键按下 S 键) set_bit(KEY_ENTER,uk_dev->keybit);//通过设置uk_dev->keybit 实现产生一个KEY_ENTER的事件 (相当于按键按下 ENTER 键) //3.注册input_dev结构体 input_register_device(uk_dev); //4.硬件相关的操作 (使用usb总线设备驱动来收发数据) //数据传输的三要素: 源 、目的、长度 //源:USB的某个端点 pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //长度: len =endpoint->wMaxPacketSize;//长度等于端点描述符的wMaxPacketSize(最大包大小) //目的:使用usb_buffer_alloc分配一个数据缓冲区 usb_buf=usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_pyhs); //使用三要素 usb requset block uk_urb = usb_alloc_urb(0, GFP_KERNEL); //使用三要素 设置usb requset block /* usb请求块, */ usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval); uk_urb->transfer_dma = usb_buf_pyhs;//设置usb传输的目的 的物理地址 uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//设置标记 //使用urb usb_submit_urb (uk_urb, GFP_KERNEL);//提交数据 提交urb return 0; }
usbmouse_as_key_irq代码如下:(当鼠标数据发生变化时执行此函数)
static void usbmouse_as_key_irq(struct urb *urb) { int i; static int cnt=0; /*将鼠标数据打印出来*/ printk("usb data %d: ",++cnt); for(i=0;i<len;i++) { printk("%02x ",usb_buf[i]); } printk("\n"); //重新提交urb usb_submit_urb (uk_urb, GFP_KERNEL);//提交数据 提交urb }
usbmouse_as_key_disconnect代码如下:其实就是usbmouse_as_key_probe的逆向操作
static void usbmouse_as_key_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); //printk("dsiconnect usbmonuse!\n"); usb_kill_urb (uk_urb);//清除urb usb_free_urb(uk_urb);//释放urb usb_buffer_free(dev,len,usb_buf,usb_buf_pyhs);//去除 usb_buf input_unregister_device(uk_dev); input_free_device(uk_dev); }
测试步骤:
1、insmod usbmonus_as_key.ko 挂载驱动
2、ls /dev/event* 查看事件
3、移动或者按下鼠标
数据1:按键值
数据2:x方向位移 数据
数据3:y方向位移 数据
数据4:滚轮数据
实验2:
目的:使用鼠标上的按键实现 L S 和ENTER 键值 在控制台上输出
通过实验一,已经将鼠标数据读了出来,所以要实现按键的话只关心第一个数据就可以了
所以只需要修改usbmouse_as_key_disconnect
static void usbmouse_as_key_irq(struct urb *urb) { static int pre_val;//用来保存上次的数据 #if 0 int i; static int cnt=0; /*将鼠标数据打印出来*/ printk("usb data %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))) { //左键发生了变化 input_event(uk_dev,EV_KEY,KEY_L,(usb_buf[0] & (1<<0))? 1 : 0);//使用input_event上报按键事件 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_event上报按键事件 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_event上报按键事件 input_sync(uk_dev);//同步信号 } pre_val = usb_buf[0]; //重新提交urb usb_submit_urb (uk_urb, GFP_KERNEL);//提交数据 提交urb }
使用/dev/tty1 测试:
使用hexdump /dev/event1 测试: