嵌入式Linux驱动学习 ——USB

1 USB基本概念

1.1 传输类型

(1)控制传输(control)
  是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。
(2)中断传输(interrupt)
  支持中断传输的典型设备有USB鼠标、 USB键盘等等。中断传输不是说usb设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。 interval是间隔时间的意思,表示usb设备希望主机多长时间来轮询自己,只要这个值确定了之后,主机就会周期性的来查看有没有数据需要处理。
(3)批量传输(bulk)
  支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。
(4)实时传输(isochronous)
  USB摄像头就是实时传输设备的典型代表,它同样进行大数量的数据传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高。

1.2 枚举过程

  USB分为主机侧和设备侧,以PC为例,PC为主机侧,插入的U盘为设备侧,当U盘插入电脑时,PC通过枚举过程,获取设备信息,并加载合适的驱动程序。在这个过程中:
  (1)新接入的USB设备的默认地址(编号)是0,在未分配新编号前,PC主机使用0地址和它通信。当分配了地址后,以后通信就用分配的地址。
  (2)所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力。
  根据电气连接的不同,区分主机侧和设备侧。
在这里插入图片描述
  区分低速设备和全速设备:
  全速设备端,D+ 线上接上拉电阻 1.5K 欧姆 ±5%
  低速设备端接,D- 线上接上拉电阻 1.5K 欧姆 ±5%
  USB主机控制器规范:OHCI、UHCI、EHCI等

1.3 框架

1.3.1 层次结构

在这里插入图片描述
1)usb设备驱动:usb设备如何与主机通信。
2)usb核心:usb驱动管理和协议处理。
3)usb主机控制器:控制插入其中的USB设备,与硬件交互。

1.3.2 设备、配置、接口、端点

《Linux设备驱动开发详解》:

USB设备的逻辑组织中,包含设备、配置、接口和端点4个层次。每个USB设备的都提供不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需要从其中选定一个),配置由多个接口组成。
在USB协议中,接口由多个端点组成,代表一个基本的功能,是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具有多个接口。设备接口是端点的汇集(Collection)。例如,USB扬声器可以包含一个音频接口以及对旋钮和按钮的接口。一个配置中的所有接口可以同时有效,并可被不同的驱动程序连接。每个接口可以有备用接口,以提供不同质量的服务参数。
端点是USB通信的最基本形式,每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每一个端点都有唯一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个USB端点只能在一个方向上承载数据,从主机到设备(输出端点)或者从设备到主机(输入端点),因此端点可看作是一个单向的管道。端点0通常为控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电,端点0就可以被访问。端点1、2等一般用作数据端点,存放主机与设备间往来的数据。

在这里插入图片描述
设备描述符:关于设备的通用信息,如供应商 ID、产品 ID 和修订 ID,支持的设备类、子类和适用的协议以及默认端点的最大包大小等。
配置描述符:此配置中的接口数、支持的挂起和恢复能力以及功率要求。
接口描述符:接口类、子类和适用的协议,接口备用配置的数目和端点数目。
端点描述符:端点地址、方向和类型,支持的最大包大小,如果是中断类型的端点则还包括轮询频率。
在这里插入图片描述

2 源码

  usb总线驱动程序的作用:
  1)识别usb
  2)查找并安装驱动程序
  3)提供usb读写函数

2.1 初始化

  插入usb设备到开发板上,会打印一些log,根据log信息,找到drivers/usb/core/hub.c的hub_port_init函数,看这个函数是如何被调用的

hub_thread --->
	hub_events ---->
		hub_port_connect_change --->
			hub_port_init   

  hub_thread函数。

static int hub_thread(void *__unused)
{

	set_freezable();

	do {
		hub_events();
		wait_event_freezable(khubd_wait,
				!list_empty(&hub_event_list) ||
				kthread_should_stop());
	} while (!kthread_should_stop() || !list_empty(&hub_event_list));

	pr_debug("%s: khubd exiting\n", usbcore_name);
	return 0;
}

  hub_thread会休眠,等待被唤醒。
  哪里被唤醒:kick_khubd

static void kick_khubd(struct usb_hub *hub)
{
	unsigned long	flags;

	/* Suppress autosuspend until khubd runs */
	atomic_set(&to_usb_interface(hub->intfdev)->pm_usage_cnt, 1);

	spin_lock_irqsave(&hub_event_lock, flags);
	if (!hub->disconnected && list_empty(&hub->event_list)) {
		list_add_tail(&hub->event_list, &hub_event_list);
		wake_up(&khubd_wait);
	}
	spin_unlock_irqrestore(&hub_event_lock, flags);
}

  当接上USB设备时,产生hub_irq中断,调用kick_khubd,唤醒hub_thread。

2.2 hub_port_connect_change

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
{
		...
		udev = usb_alloc_dev(hdev, hdev->bus, port1); //分配usb_device
		
		usb_set_device_state(udev, USB_STATE_POWERED);//设置状态

			choose_address(udev); //分配地址
			
		status = hub_port_init(hub, udev, port1, i);//初始化
		
			status = usb_new_device(udev);//创建设备
		...
}

(1)usb_alloc_dev

struct usb_device *usb_alloc_dev(struct usb_device *parent,
				 struct usb_bus *bus, unsigned port1)
{
	...
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);//分配结构体
	..

	device_initialize(&dev->dev);//初始化
	dev->dev.bus = &usb_bus_type;//usb总线
	dev->dev.type = &usb_device_type;
	dev->dev.groups = usb_device_groups;
	dev->dev.dma_mask = bus->controller->dma_mask;
	set_dev_node(&dev->dev, dev_to_node(bus->controller));
	dev->state = USB_STATE_ATTACHED;
	...
}

(2)choose_address
  找到要分配的地址,此时还没有分配。

static void choose_address(struct usb_device *udev)
{
	int		devnum;
	struct usb_bus	*bus = udev->bus;

	if (udev->wusb) {
		devnum = udev->portnum + 1;
		BUG_ON(test_bit(devnum, bus->devmap.devicemap));
	} else {
		devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
					    bus->devnum_next);//查找非0的编号
		if (devnum >= 128)//没找到,再找一次
			devnum = find_next_zero_bit(bus->devmap.devicemap,
						    128, 1);
		bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
	}
	if (devnum < 128) {
		set_bit(devnum, bus->devmap.devicemap);
		udev->devnum = devnum;
	}
}

  usb最大支持127个设备。
(3)hub_port_init

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{
	...
				retval = hub_set_address(udev, devnum); //设置地址
				
		retval = usb_get_device_descriptor(udev, 8);//获取设备描述符,只读了前面8个字节,需要先获取总容量
	...
}

  hub_set_address设置地址,通过usb_control_msg发送报文给usb设备。
(4)usb_new_device

int usb_new_device(struct usb_device *udev)
{
	...
	err = usb_configure_device(udev);	//把所有的描述符都读出来
	...
	err = device_add(&udev->dev);//添加设备
	...
}

  类似platform总线,usb也有总线。当添加设备时,就会调用usb_device_match,与id_table进行匹配,匹配成功则调用probe函数。
在这里插入图片描述
  以/drivers/hid/usbhid/usbmouse.c中的usb_mouse_id_table为例,就是要设置usb支持的信息。

#define USB_INTERFACE_INFO(cl, sc, pr) \
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
	.bInterfaceClass = (cl), \
	.bInterfaceSubClass = (sc), \
	.bInterfaceProtocol = (pr)
	
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 */
};

  上述:USB_INTERFACE_CLASS_HID,接口类型为HID;USB_INTERFACE_SUBCLASS_BOOT,接口子类型为启动设备;USB_INTERFACE_PROTOCOL_MOUSE,接口协议为USB鼠标的协议。如果usb设备与id_table中描述一致,那么就可以支持这个设备。

2.3 总结

上述流程:
1)hub_thread–>hub_events,休眠
2)中断–>hub_irq,调用hub_port_connect_change
3)在hub_port_connect_change中依次调用了:usb_alloc_dev、choose_address、hub_port_init、usb_new_device

3 程序

  实验目的:USB鼠标,左键——L,右键——S,滚轮——enter。
  步骤:
  1)分配/设置usb_driver
  2)注册
  参考:/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	
	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)))
	{
		/* 左键发生了变化 */
		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);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

	/* 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);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

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

	/* 目的: */
	usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &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);
	
	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");

4 测试

(1)内核

 Device Drivers ---> 
 	HID Devices 
		<> USB Human Interface Device (full HID) support

(2)usb 1-1: device descriptor read/64, error -62
  UPLLCON没有设置好。根本问题在uboot。但是可以修改内核来弥补。在drivers/usb/host/ohci-s3c2410.c 的函数 s3c2410_start_hc修改如下:

#include <mach/regs-clock.h>  //添加的代码
 
static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd)
{
	struct s3c2410_hcd_info *info = dev->dev.platform_data;
 
	unsigned long upllvalue  = (0x38<< 12) | (0x02 << 4) | (0x01); // 添加的代码
	unsigned long upllvalue1 = (0x38<< 12) | (0x02 << 4) | (0x02); // 添加的代码
 
	dev_dbg(&dev->dev, "s3c2410_start_hc:\n");
 
	__raw_writel(upllvalue, S3C2410_UPLLCON);  // 添加的代码
	mdelay(20);				// 添加的代码
	__raw_writel(upllvalue1, S3C2410_UPLLCON);// 添加的代码
	mdelay(20);				// 添加的代码
 
 
	clk_enable(usb_clk);
	mdelay(2);			/* let the bus clock stabilise */
 
	clk_enable(clk);
 
	if (info != NULL) {
		info->hcd	= hcd;
		info->report_oc = s3c2410_hcd_oc;
 
		if (info->enable_oc != NULL) {
			(info->enable_oc)(info, 1);
		}
	}
}

(3)测试
  用新的内核,并加载驱动,测试。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值