USB也是主从结构的总线。整体呈树状结构。
USBHOST,由它发起传输。
USB设备,分为HUB和FUNCTION。我们所用的外设,就是FUNCTION。
USB树中,最多能有6层。
USB设备地址,是7位的。其中,地址0是一个特殊地址,SFA(special function address)。所以,理论上,USBBUS上,只有127个独立地址。
USB设备,大体上可以分为物理设备和逻辑设备。
一个USB物理设备,可以由一个或者多个逻辑设备组成。
一个逻辑设备,体现为一个或者多个接口(Interface),一个接口则由一组Endpoint组成。每个EP都有一个地址,地址是4位的。从HOST的角度看,EP由分为INPUTEP和OUTPUTEP,注意,INEP和OUTEP的方向定义,是从HOST的角度看的,如果HOST发送数据给设备,那么就是OUTEP,如果设备发送数据给HOST,那么就是INEP。
EP0是一个特殊端点,SFE(special function endpoint)。
用户程序中的Buffer和一个EP之间建立了通信连接后,就构成了一个pipe。
USB传输分为四类:
1)控制传输。突发的,非周期性的,主要用于命令和状态的操作。EP0默认用于控制传输,所以EP0也叫做控制端点。
2)等时传输,也叫做同步传输。周期性,连续的,对实时有要求,但是对数据正确性要求不高的场合,例如音频,如果不能满足实时要求,声音会卡顿,但是如果数据少量错误,声音信号并不会过多受损。
3)中断传输,也叫做间隔传输。通常用于键盘鼠标。少量的数据,穿插在其他的传输业务中间传输。
4)块传输。也叫做批量传输。非周期性,大量数据,主要用于对传输延时要求宽松的情况,比如存储设备。
每一个EP都有自己的传输类型。所以对应的,就有控制端点,等时端点,中断端点,块端点的称谓。
来看看linux中的usb驱动架构。
自底向上,
usb驱动分为USBHOSTDRIVER,USBCORE,USBDEVIECEDRIVER。
类似于I2C和SPI,USB的CORE提供API,为USBDEVICE提供服务。而USBCORE又调用USBHOST的驱动,使用USBHOST的服务。
如果linux管理的硬件平台是HOST,那么就是上述架构了。
但是还有一种情况,linux管理的硬件平台中,不是USBHOSTController,而是USBDEVICEController。那么就是另外一种驱动架构了。
自底向上,
USB驱动分为UDC驱动,GadgetFunctionAPI,GadgetFunctionDriver.
这里,我们讨论第一种,也是最常见的情况,USBHOSTController。
首先来看看linux中的描述符。
struct usb_device是描述USB设备的,但是它是一个控制块。
另外有几个描述块,
struct usb_device_descriptor;
struct usb_config_descriptor;
struct usb_interface_descriptor;
struct usb_endpoint_descriptor;
struct usb_string_descriptor;
之所以他们被成为DB而不是CB,是因为他们的成员都是单纯的数据,他们只用来记录信息。
当用户在SHELL中使用lsusb命令查看usb设备的信息时,显示的就是关联的这些描述符中记录的信息。
来看看UHC的驱动。
linux中,用usb_hcd结构体描述USB主控器。
struct usb_hcd{
struct usb_bus self;
struct urb* status_urb;
struct timer_list rh_timer;
struct usb_phy* usb_phy;
struct phy * phy;
unsigned long flags;
const struct hc_driver* driver;
void* hcd_priv;
...
};
从中可以看出,usb_hcd中,关联着hc_driver。它是对应的驱动接口。
内核提供了相关的API。
usb_create_hcd,
usb_add_hcd,
usb_remove_hcd,
用来向内核注册注销hcd。
struct hc_driver{
...
(*irq)(...);
(*start)(...);
(*stop)(...);
(*urb_enqueue)(...);
(*urb_dequeue)(...);
(*add_endpoint)(...);
(*drop_endpoint)(...);
...
};
其中有一个关键的函数指针,urb_enqueue,当上层通过usb_submit_urb()提交一个URB请求后,该函数会调用usb_hcd_submit_urb(),最终,会调用hc_driver中的urb_enqueue函数。
usb_hcd中有一个成员,hcd_priv,这是一个通配句柄,关联到hcd的私有数据对象。
(这里简单介绍以下SOD中的这个技巧。
我们会在很多结构体中看到xxxpriv或者xxxprivate_data这样的句柄。被称为私有数据。
实际上,我们并不是用它来存放数据,而是用它来存放一个句柄,作为实体标签。
所谓的私有,也并不是这个对象是结构体的一部分,而是说,这个被标记的对象,是本对象在实际运行时,特别感兴趣,需要经常使用到的对象。
之所以不能作为一个明确的成员,赋予特定的类型,而必须赋予通配类型,是因为这个特别关注的对象,并不一定是某种类型的。
最常见的,就是利用私有数据这个通配句柄,来完成回溯引用,形成环路。或者标记到整个子系统中,最关键的对象上,该对象拥有最强大的索引能力,完成源端引用。
例如,FILE中的private_data,总是被填充为某个衍生设备的句柄。这样,当内核调用操作函数时,会传入FILE的句柄,那么操作函数,就可以利用FILE索引到衍生设备的控制块,从而可以从衍生设备的控制块中获取感兴趣的数据。)
ehci_hcd通常被作为usb_hcd的私有数据而被标记。
drivers/usb/host/ehci-hcd.c文件中,实现了绝大多数EHCI主机驱动的工作。
EHCI驱动,被填充到一个hc_driver中,最终注册到内核中。
static const struct hc_driver ehci_hc_driver = {
...
.reset = ehci_setup,
.start = ehci_run,
.stop = ehci_stop,
.shutdown = ehci_shutdown,
...
};
UHC的驱动,通常由SOC厂商提供,我们并不需要关心。
我们需要关心的是,如何设计USBDEV。
USBDEV,对应于USBBUS上的设备。
在linux中,USBDEV请求内核的USBCORE的服务,而USBCORE则调用UHCDRIVER的服务,在USBBUS上产生操作时序。
linux实现了几类通用的USBDEV的DRIVER,例如:
音频设备,通信设备,HID设备,显示设备,存储设备,电源设备,打印设备,HUB设备等。
大多数的通用的USBDEV,不需要再编写驱动,而特定厂商,特定芯片的驱动,往往也可以参考内核中已有的驱动模板。
类似于I2CBUS,SPIBUS,
USBBUS中的usb_device,也对应有一个usb_driver。
struct usb_driver{
const char* name;
...
(*probe)(...);
(*disconnect)(...);
...
const struct usb_device_id* id_table;
...
};
主要就是probe和disconnet,当有设备被探测到时,调用probe,用于初始化软硬件资源。当设备拔出时,释放资源。
id_table指向一个usb_device_id数组。
内核提供了一组宏,来生成usb_device_id。
USB_DEVICE(vendor, product),
USB_DEVICE_VER(vendor, product, low, high),
USB_DEVICE_INFO(class, subclass, protocol),
USB_INTERFACE_INFO(class, subclass, protocol)
来看一个实例
static struct usb_device_id id_table[] ={
{
USB_DEVICE(VENDOR_ID, PRODUCT_ID)
},
{}
};
MODULE_DEVICE_TABLE(usb, id_table);
当USBCORE检测到某个设备的属性和某个驱动的id_table中的属性一致时,驱动的probe就会被调用。在probe里,再完成衍生设备的UADEV的注册,UADEV的DRIVER的注册等。
类似于SPIBUS中的msg,
USBDEV请求内核服务,使用的载子,被定义为URB。URB也类似于NDEV中使用的载子skbuff。
struct urb{
struct list_head urb_list;
struct usb_host_endpoint* ep;
unsigned int pipe;
unsigned int stream_id;
unsigned char* setup_packet;
int number_of_packets;
int interval;
void* transfer_buffer;
u32 transfer_buffer_length;
u32 actual_length;
unsigned int transfer_flags;
usb_complete_t complete;
void* context;
int status;
...
};
从中可以看出,urb内嵌链节,所以可以构成链表。
ep关联到一个EP的控制块。
pipe是EP和用户程序建立的管道的IDNUM。
streamID是EP和用户程序建立的stream的IDNUM。
USBDEV中的每个EP,都处理一个URB队列。
内核提供的操作URB的API如下:
struct urb* usb_alloc_urb(int iso_packets, gfp_t mem_flags);
创建一个urb对象。
void usb_free_urb(struct urb* urb);
释放一个urb对象。
void usb_fill_control_urb(struct urb* urb, struct usb_device* dev, unsigned int pipe, unsigned char* setup_packet, void* transfer_buffer, int buffer_len, usb_complete_t complete_fn, void* context);
用来初始化填充一个控制型URB。
void usb_fill_int_urb(struct urb* urb, struct usb_device* dev, unsigned int pipe, void* transfer_buffer, int buffer_len, usb_complete_t complete_fn, void* context, int interval)
用来初始化填充一个中断型URB。
void usb_fill_bulk_urb(struct urb* urb, struct usb_device* dev, unsigned int pipe, void* transfer_buffer, int buffer_len, usb_complete_t complete_fn, void* context)
用来初始化填充一个批量型URB。
注意,等时型URB,没有对应的API,所以,等时型URB只能在创建后,手工填充。
int usb_submit_urb(struct urb* urb, gfp_t mem_flags);
提交给USBCORE,USBCORE会根据URB来分配缓冲区。
提交后,当前进程被休眠,使用完成量或者等待队列来同步。
USBHOST的驱动负责处理提交的URB,当URB完成时,USBHOST的驱动通知USBDEV。
URB完成,有三种可能情况。
1)URB成功发送给设备,并返回正确的确认。urb->status ==0
2)URB传输时,发生了错误。urb->status != 0
3)URB在排队时,被USBCORE删除。例如驱动使用了urb_unlink_urb()或者urb_kill_urb()函数。
这三种情况,都会让内核调用“完成回调函数”。
urb->complete(context)。
URB传输完成后,内核会调用“完成回调函数”,通常在Callback里唤醒被阻塞休眠的进程。
当进程被唤醒后,继续执行,会检查URB的status。
整个流程总结如下:
usb_alloc_urb(),==>
usb_fill_xxx_urb(),==>
usb_submit_urb(),==>
urb->complete().
有一些简单的API,完成整个简易的流程。
int usb_bulk_msg(struct usb_device* usb_dev, unsigned int pipe, void*data, int len, int * actual_length, int timeout);
调用这个函数的进程,中间会被休眠,并被唤醒,当函数返回时,如果返回值为0,则调用成功,否则,返回一个负数。
int usb_control_msg(struct usb_device* usb_dev, unsigned int pipe, __u8 request, __u8 request_type, __u16 value, __u16 index, void* data, int len, __u16 size, int timeout);
调用这个函数的进程,中间会被休眠,并被唤醒,当函数返回时,如果返回值为0,则调用成功,否则,返回一个负数。
int usb_interrupt_msg(struct usb_device* usb_dev, unsigned int pipe, void*data, int len, int * actual_length, int timeout);
调用这个函数的进程,中间会被休眠,并被唤醒,当函数返回时,如果返回值为0,则调用成功,否则,返回一个负数。
用户程序和USBDEV的EP之间,是通过pipe来通信的。当USBDRIVER从Interface中获取了EP的信息后,就可以创建管道了。
内核提供了与pipe相关的宏拟函数API。
usb_sndctrlpipe(dev, endpoint)
usb_sndisopipe(dev, endpoint)
usb_sndintpipe(dev, endpoint)
usb_sndbulkpipe(dev, endpoint)
usb_rcvctrlpipe(dev, endpoint)
usb_rcvisopipe(dev, endpoint)
usb_rcvintpipe(dev, endpoint)
usb_rcvbulkpipe(dev, endpoint)
usb_driver中probe函数,应该完成对象创建,填充,并注册到内核等工作。
通常要用到的API有:
void usb_set_intfdata(struct usb_interface* intf, void* data);
设置usb_interface的私有数据。
void* usb_get_intfdata(struct usb_interface* intf);
获取usb_interface的私有数据。
struct usb_device* interface_to_usbdev(struct usb_interface* intf);
从usb_interface中获取关联的usb_device的句柄。
整个驱动分为几个部分:
1)Derived_DEV_CB定义,并实例化。
2)UADEV的DRIVER的实例化,并填充。
3)UADEV的相关驱动操作函数编写,分为机制性函数的编写和事务性函数的编写。
4)USBDRIVER的实例化,并填充,注册到内核中。
5)IDTABLE的实例化,注册到内核中。
6)probe函数编写,在其中注册用户可访问设备(User Accessable Device),如CDEV,BDEV,NDEV等。及其对应的DRIVER。
7)remove函数编写,在其中逆操作。
可以看出,与常规的UADEV的编写相比,多了几个部分,就是与BUSDEV相关的DRIVER。
drivers/usb/usb-skeleton.c中,为我们提供了一个USB驱动的骨架程序。
来看一个具体的实例。
#define PDIUSBD12_MAJOR 256
#define PDIUSBD12_MINOR 10
#define PDIUSBD12_DEV_NAME "pdiusbd12"
struct pdiusbd12_dev{
struct cdev dev;
dev_t dev;
struct usb_device* usbdev;
struct urb* ep2inurb;
int errors;
wait_queue_head_t * wq;
int pipe_ep1_out;
int pipe_ep1_in;
int pipe_ep2_out;
int pipe_ep2_in;
int maxp_ep1_out;
int maxp_ep1_in;
int maxp_ep2_out;
int maxp_ep2_in;
unsigned int ep2inlen;
unsigned char ep1inbuf[16];
unsigned char ep1outbuf[16];
unsigned char ep2inbuf[64];
unsigned char ep2outbuf[64];
};
static struct pdiusbd12_dev* pdiusbd12;
static unsigned int minor = PDIUSBD12_MINOR;
static struct file_operations pdiusbd12_ops = {
.owner = THIS_MODULE,
.open = pdiusbd12_open,
.release = pdiusbd12_release,
.read = pdiusbd12_read,
.write = pdiusbd12_write,
.unlocked_ioctl = pdiusbd12_ioctl,
};
static int pidusbd12_open(struct inode* inode, struct file* filp)
{
struct pdiusbd12_dev* pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);
filp->private_data = pdiusbd12;
return 0;
}
static int pidusbd12_release(struct inode* inode, struct file* filp)
{
struct pdiusbd12_dev* pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);
usb_kill_urb(pdiusbd12->ep2inurb);
return 0;
}
static ssize_t pdiusbd12_read(struct file* filp, char __user * buf, size_t count, loff_t* f_ops)
{
int ret;
struct pdiusbd12_dev* pdiusbd12 = filp->private_data;
struct usb_device* usbdev = pdiusbd12->usbdev;
ret = count;
usb_fill_bulk_urb(pdiusbd12->ep2inurb, usbdev,
pdiusbd12->pipe_ep2_in, pdiusbd12->ep2inbuf,
ret, usb_read_complete, pdiusbd12);
ret = usb_submit_urb(pdiusbd12->ep2inurb, GFP_KERNEL);
interruptable_sleep_on(&pdiusbd12->wq);
ret = copy_to_user(buf, pdiusbd12->ep2inbuf, pdiusbd12->ep2inlen);
return ret;
}
void usb_read_complete(struct urb* urb)
{
struct pdiusbd12_dev* pdiusbd12 = urb->context;
switch(urb->status){
case 0:
pdiusbd12->ep2inlen = urb->actual_length;
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
default:
pdiusbd12->ep2inlen = 0;
break;
}
pdiusbd12->errors = urb->status;
wake_up_interruptible(&pdiusbd12->wq);
}
static ssize_t pdiusbd12_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
int ret;
int len;
struct pdiusbd12_dev* pdiusbd12 = filp->private_data;
ret = copy_from_user(pdiusbd12->ep2outbuf, buf, count);
ret = usb_bulk_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep2_out, pdiusbd12->ep22outbuf, count, &len, 10*HZ);
return ret;
}
long pdiusbd12_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
int ret;
int len;
struct pdiusbd12_dev* pdiusbd12 = filp->private_data;
switch(cmd){
case PDIUSBD12_GET_KEY:
ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_in,
pdiusbd12->ep1inbuf, 8,
&len, 10*HZ);
ret = copy_to_user((unsigned char __user *)arg, pdiusbd12->ep1inbuf, len);
break;
case PDIUSBD12_SET_LED:
ret = copy_from_user(pdiusbd12->ep1outbuf, (unsigned char __user *)arg, 8);
ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_out,
pdiusbd12->ep1outbuf, 8
&len, 10*HZ);
break;
default:
break;
}
return 0;
}
static struct usb_driver pdiusbd12_driver = {
.name = "pdiusbd12",
.probe = pdiusbd12_probe,
.disconnect = pdiusbd12_disconnect,
.id_table = id_table,
};
module_usb_driver(pdiusbd12_driver);
static struct usb_device_id id_table[] = {
{USB_DEVICE(0x8888, 0x000b)},
{}
};
MODULE_DEVICE_TABLE(usb, id_table);
int pdiusbd12_probe(struct usb_interface *intf, const struct usb_device_id* id)
{
struct usb_device* usbdev;
struct usb_host_interface* interface;
struct usb_endpoint_descriptor* ep_desc;
int ret = 0;
pdiusbd12 = kmalloc(sizeof(struct pdiusbd12_dev), GFP_KERNEL);
usbdev = interface_to_usbdev(intf);
interface = intf->cur_altsetting;
ep_desc = &interface->endpoint[0].desc;
pdiusbd12->pipe_ep1_in = usb_rcvintpipe(usbdev, ep_desc->bEndpointAddress);
pdiusbd12->maxp_ep1_in = usb_maxpacket(usbdev,
pdiusbd12->pipe_ep1_in,
usb_pipeout(pdiusbd12->pipe_ep1_in));
ep_desc = &interface->endpoint[1].desc;
pdiusbd12->pipe_ep1_out = usb_sndintpipe(usbdev, ep_desc->bEndpointAddress);
pdiusbd12->maxp_ep1_out = usb_maxpacket(usbdev,
pdiusbd12->pipe_ep1_out,
usb_pipeout(pdiusbd12->pipe_ep1_out));
ep_desc = &interface->endpoint[2].desc;
pdiusbd12->pipe_ep2_in = usb_rcvbulkpipe(usbdev, ep_desc->bEndpointAddress);
pdiusbd12->maxp_ep2_in = usb_maxpacket(usbdev,
pdiusbd12->pipe_ep2_in,
usb_pipeout(pdiusbd12->pipe_ep2_in));
ep_desc = &interface->endpoint[3].desc;
pdiusbd12->pipe_ep2_out = usb_sndbulkpipe(usbdev, ep_desc->bEndpointAddress);
pdiusbd12->maxp_ep2_out = usb_maxpacket(usbdev,
pdiusbd12->pipe_ep2_out,
usb_pipeout(pdiusbd12->pipe_ep2_out));
pdiusbd12->ep2inurb = usb_alloc_urb(0, GFP_KERNEL);
pdiusbd12->usbdev = usbdev;
usb_set_intfdata(intf, pdiusbd12);
pdiusbd12->dev = MKDEV(PDIUSBD12_MAJOR, minor++);
ret = register_chrdev_region(pdi_usbd12->dev, 1, PDIUSBD12_DEV_NAME);
cdev_init(&pdiusbd12->cdev, &pdiusbd12_ops);
pdiusbd12->cdev.owner = THIS_MODULE;
ret = cdev_add(&pdiusbd12->cdev, pdiusbd12->dev, 1);
init_waitqueue_head(&pdiusbd12->wq);
return ;
}
void pdiusbd12_disconnect(struct usb_interface* intf)
{
struct pdiusbd12_dev* pdiusbd12 = usb_get_intfdata(intf);
cdev_del(&pdiusbd12->cdev);
unregister_chrdev_region(pdiusbd12->dev, 1);
usb_kill_urb(pdiusbd12->ep2inurb);
usb_free_urb(pdiusbd12->ep2inurb);
kfree(pdiusbd12);
return;
}
在SHELL中输入命令可以测试。
#make
#make modules_install
#depmod
#modprobe pdiusbd12
#mknod /dev/pdiusbd12 c 256 10
#gcc -o test test.c
#./test