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)测试
用新的内核,并加载驱动,测试。