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*/<span style="color: rgba(0, 0, 255, 1)">struct</span> usb_device *dev =<span style="color: rgba(0, 0, 0, 1)"> interface_to_usbdev(intf); </span><span style="color: rgba(0, 0, 255, 1)">struct</span> usb_host_interface *<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">struct</span> usb_endpoint_descriptor *<span style="color: rgba(0, 0, 0, 1)">endpoint; </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> pipe; </span><span style="color: rgba(0, 0, 255, 1)">interface</span> = intf->cur_altsetting;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">interface结构体 </span>
endpoint </span>= &<span style="color: rgba(0, 0, 255, 1)">interface</span>->endpoint[<span style="color: rgba(128, 0, 128, 1)">0</span>].desc;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">端点描述符</span>
#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<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">1.分配一个input_dev 结构体</span> uk_dev =<span style="color: rgba(0, 0, 0, 1)"> input_allocate_device(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">2.设置分配的这个结构体 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">2.1能产生那类事件</span> set_bit(EV_KEY, uk_dev->evbit);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置能够产生按键类事件uk_dev->evbit是表示能够产生那类事件</span> set_bit(EV_REP, uk_dev->evbit);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置能够产生重复类事件uk_dev->evbit是表示能够产生那类事件 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">2.2能产生哪些事件</span> set_bit(KEY_L,uk_dev->keybit);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通过设置uk_dev->keybit 实现产生一个KEY_L的事件 (相当于按键按下 L 键)</span> set_bit(KEY_S,uk_dev->keybit);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通过设置uk_dev->keybit 实现产生一个KEY_S的事件 (相当于按键按下 S 键)</span> set_bit(KEY_ENTER,uk_dev->keybit);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通过设置uk_dev->keybit 实现产生一个KEY_ENTER的事件 (相当于按键按下 ENTER 键) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">3.注册input_dev结构体</span>
input_register_device(uk_dev);
//4. 硬件相关的操作 ( 使用 usb 总线设备驱动来收发数据)
//数据传输的三要素: 源 、目的、长度
//源:USB 的某个端点
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">长度:</span> len =endpoint->wMaxPacketSize;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">长度等于端点描述符的wMaxPacketSize(最大包大小) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">目的:使用usb_buffer_alloc分配一个数据缓冲区</span> usb_buf=usb_buffer_alloc(dev, len, GFP_ATOMIC, &<span style="color: rgba(0, 0, 0, 1)">usb_buf_pyhs); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用三要素 usb requset block</span> uk_urb = usb_alloc_urb(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, GFP_KERNEL); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用三要素 设置usb requset block</span> <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> usb请求块, </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint</span>-><span style="color: rgba(0, 0, 0, 1)">bInterval); uk_urb</span>->transfer_dma = usb_buf_pyhs;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置usb传输的目的 的物理地址</span> uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置标记 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用urb</span> usb_submit_urb (uk_urb, GFP_KERNEL);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">提交数据 提交urb</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
}
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;</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">将鼠标数据打印出来</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> printk(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">usb data %d: </span><span style="color: rgba(128, 0, 0, 1)">"</span>,++<span style="color: rgba(0, 0, 0, 1)">cnt); </span><span style="color: rgba(0, 0, 255, 1)">for</span>(i=<span style="color: rgba(128, 0, 128, 1)">0</span>;i<len;i++<span style="color: rgba(0, 0, 0, 1)">) { printk(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">%02x </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,usb_buf[i]); } printk(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
#endif
<span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> * USB鼠标数据含义 * data[0]:bit0-左键 1-按下 0-松开 * bit1-右键 1-按下 0-松开 * bit2-中键 1-按下 0-松开 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">if</span>((pre_val & (<span style="color: rgba(128, 0, 128, 1)">1</span><<<span style="color: rgba(128, 0, 128, 1)">0</span>)) != (usb_buf[<span style="color: rgba(128, 0, 128, 1)">0</span>] & (<span style="color: rgba(128, 0, 128, 1)">1</span><<<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">))) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">左键发生了变化</span> input_event(uk_dev,EV_KEY,KEY_L,(usb_buf[<span style="color: rgba(128, 0, 128, 1)">0</span>] & (<span style="color: rgba(128, 0, 128, 1)">1</span><<<span style="color: rgba(128, 0, 128, 1)">0</span>))? <span style="color: rgba(128, 0, 128, 1)">1</span> : <span style="color: rgba(128, 0, 128, 1)">0</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用input_event上报按键事件</span> input_sync(uk_dev);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">同步信号</span>
}
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 测试: