一、基本概念
现象:把USB设备接到PC
1. 右下角弹出"发现android phone"
2. 跳出一个对话框,提示你安装驱动程序(设备驱动程序)
问1. 既然还没有"驱动程序",为何能知道是"android phone"
答1. windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone"
USB总线驱动程序负责:识别USB设备, 给USB设备找到对应的驱动程序
问2. PC机上接有非常多的USB设备,怎么分辨它们?
答2. 每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)
问3. USB设备刚接入PC时,还没有编号;那么PC怎么把"分配的编号"告诉它?
答3. 新接入的USB设备的默认编号是0,在未分配新编号前,PC使用0编号和它通信。
问4. 为什么一接入USB设备,PC机就能发现它?
答4. PC的USB口内部,D-和D+接有15K的下拉电阻,未接USB设备时为低电平
USB设备的USB口内部,D-或D+接有1.5K的上拉电阻;它一接入PC,就会把PC USB口的D-或D+拉高,从硬件的角度通知PC有新设备接入
二、其他概念:
1. USB是主从结构的
所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力。
例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。
2. USB的传输类型:
a. 控制传输:可靠,时间有保证,比如:USB设备的识别过程
b. 批量传输: 可靠, 时间没有保证, 比如:U盘
c. 中断传输:可靠,实时,比如:USB鼠标
d. 实时传输:不可靠,实时,比如:USB摄像头
3. USB传输的对象:端点(endpoint)
我们说"读U盘"、"写U盘",可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据
除了端点0外,每一个端点只支持一个方向的数据传输
端点0用于控制传输,既能输出也能输入
4. 术语里、程序里说的输入(IN)、输出(OUT) "都是" 基于USB主机的立场说的。
比如鼠标的数据是从鼠标传到PC机, 对应的端点称为"输入端点"
5.USB驱动程序框架:
app:
-------------------------------------------
USB设备驱动程序 // 知道数据含义
内核 --------------------------------------
USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数
-------------------------------------------
USB主机控制器
UHCI OHCI EHCI //UHCI:低速(1.5Mbps)/全速(12),OHCI:低速/全速,EHCI:高速(480)
硬件 -----------
USB设备
6.怎么写USB设备驱动程序?
1)分配/设置usb_driver结构体
.id_table
.probe
.disconnect
2)注册
3)具体操作:
make menuconfig去掉原来的USB鼠标驱动
-> Device Drivers
-> HID Devices
<> USB Human Interface Device (full HID) support
make uImage 并使用新的内核启动
insmod usbmouse_as_key.ko按照驱动
ls /dev/event*检查设备
接上USB鼠标
ls /dev/event*
cat /dev/tty1 操作鼠标观察数据
hexdump /dev/event0 操作鼠标观察数据
三、USB鼠标设备驱动程序
#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; //USB数据缓冲区
static dma_addr_t usb_buf_phys; //USB数据缓冲区的物理地址
static int len; //USB数据包的长度
static struct urb *uk_urb; //USB请求块(URB),用于USB通信
//USB设备ID数组,用于匹配特定类型的USB鼠标设备
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接口类为HID(Human Interface Device,人机接口设备),子类为BOOT,协议为MOUSE(鼠标)
};
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val; //存储前一次的 USB 鼠标数据状态
/* 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];
usb_submit_urb(uk_urb, GFP_KERNEL); //重新提交urb,以便继续接收 USB 鼠标数据并处理下一次的状态变化
}
//Linux内核发现匹配id_table的USB设备时被调用
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; //USB接口描述符
struct usb_endpoint_descriptor *endpoint; //USB端点描述符
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc; //除端点0的第一个端点
uk_dev = input_allocate_device(); //1.分配一个input_dev,表示输入设备
set_bit(EV_KEY, uk_dev->evbit); //设置能产生哪类事件:键盘事件(EV_KEY)和重复事件(EV_REP)
set_bit(EV_REP, uk_dev->evbit);
set_bit(KEY_L, uk_dev->keybit); //指定了输入设备可以生成的按键事件
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
input_register_device(uk_dev); //2.注册,将其纳入Linux内核的输入子系统中,以便处理输入事件
//3.硬件相关操作
//数据传输3要素: 源,目的,长度
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //源: USB设备的某个端点
len = endpoint->wMaxPacketSize; //长度
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); //目的,缓冲区将用于接收USB鼠标设备发送的数据
// 使用"3要素
uk_urb = usb_alloc_urb(0, GFP_KERNEL); //分配USB请求块
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval); //USB传输完成后调用usbmouse_as_key_irq回调函数,用于处理接收到的USB数据
uk_urb->transfer_dma = usb_buf_phys; //确保数据传输时不需要进行DMA映射
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_submit_urb(uk_urb, GFP_KERNEL); //提交USB请求块(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");