USB设备驱动

一、基本概念

现象:把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");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值