linux设备驱动之USB数据传输分析 一
2024-07-18 来源:cnblogs
三:传输过程的实现
说到传输过程,我们必须要从URB开始说起,这个结构的就好比是网络子系统中的skb,好比是I/O中的bio.USB系统的信息传输就是打成URB结构,然后再过行传送的.
URB的全称叫USB request block.下面从它的接口说起.
3.1:URB的相关接口
1:URB的创建
URB的创建是由usb_alloc_urb()完成的.这个函数会完成URB内存的分配和基本成员的初始化工作.代码如下:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
{
struct urb *urb;
urb = kmalloc(sizeof(struct urb) +
iso_packets * sizeof(struct usb_iso_packet_descriptor),
mem_flags);
if (!urb) {
err('alloc_urb: kmalloc failed');
return NULL;
}
usb_init_urb(urb);
return urb;
}
这个函数有两个参数,一个是iso_packets.仅仅用于ISO传输.表示ISO数据包个数,如果用于其它类型的传输,此参数为0.另一个是mem_flags.是分配内存的参数.
Usb_init_urb()如下:
void usb_init_urb(struct urb *urb)
{
if (urb) {
memset(urb, 0, sizeof(*urb));
kref_init(&urb->kref);
INIT_LIST_HEAD(&urb->anchor_list);
}
}
由此可以看到,它的初始化只是初始化了引用计数和ahchor_list链表.这个链表在URB被锁定的时候会用到.
2:URB的初始化
USB2.0 spec中定义了四种传输,为别为ISO,INTER,BULK,CONTORL.linux kernel为INTER,BULK,CONTORL的URB初始化提供了一些API,ISO的传输只能够手动去初始化.这些API如下:
static inline void usb_fill_control_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
unsigned char *setup_packet,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context)
static inline void usb_fill_bulk_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context)
static inline void usb_fill_int_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context,
int interval)
分别用来填充CONTORL,BULK,INT类型的URB.
观察他们的函数原型,发现有很多相的的参数.先对这些参数做一下解释:
Urb:是要初始化的urb
Dev:表示消息要被发送到的USB设备
Pipe:表示消息被发送到的端点
transfer_buffer:表示发送数据的缓冲区
length:就是transfer_buffer所表示的缓冲区大小
context:完成处理函数的上下文
complete_fn:传输完了之后要调用的函数.
usb_fill_control_urb()的setup_packet:即将被发送到端点的设备数据包
usb_fill_int_urb()中的interval:这个urb应该被调度的间隔.
函数的实际都是差不多的.以usb_fill_control_urb()为例:
static inline void usb_fill_control_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
unsigned char *setup_packet,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context)
{
urb->dev = dev;
urb->pipe = pipe;
urb->setup_packet = setup_packet;
urb->transfer_buffer = transfer_buffer;
urb->transfer_buffer_length = buffer_length;
urb->complete = complete_fn;
urb->context = context;
}
如上所示,只是将函数的参数赋值给了URB相关的成员而已.
另外,关于ISO的URB初始化虽然没有可以调用的API,但它的初始化也很简单,对应就是填充几个成员而已.
另外,对于pipe的参数.有一系列辅助的宏.如下示:
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint)
((PIPE_CONTROL
#define usb_rcvctrlpipe(dev,endpoint)
((PIPE_CONTROL
#define usb_sndisocpipe(dev,endpoint)
((PIPE_ISOCHRONOUS
#define usb_rcvisocpipe(dev,endpoint)
((PIPE_ISOCHRONOUS
#define usb_sndbulkpipe(dev,endpoint)
((PIPE_BULK
#define usb_rcvbulkpipe(dev,endpoint)
((PIPE_BULK
#define usb_sndintpipe(dev,endpoint)
((PIPE_INTERRUPT
#define usb_rcvintpipe(dev,endpoint)
((PIPE_INTERRUPT
这个宏都是根据usb2.0 spec的规范来设计的.
3:提交URB
提交urb的接口是usb_submit_urb().代码如下:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
int xfertype, max;
struct usb_device *dev;
struct usb_host_endpoint *ep;
int is_out;
if (!urb || urb->hcpriv || !urb->complete)
return -EINVAL;
dev = urb->dev;
if ((!dev) || (dev->state
return -ENODEV;
/* For now, get the endpoint from the pipe. Eventually drivers
* will be required to set urb->ep directly and we will eliminate
* urb->pipe.
*/
//取得要传输的端口.对端地址是由方向+dev address+port number组成的
ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out)
[usb_pipeendpoint(urb->pipe)];
if (!ep)
return -ENOENT;
urb->ep = ep;
urb->status = -EINPROGRESS;
urb->actual_length = 0;
/* Lots of sanity checks, so HCDs can rely on clean data
* and don't need to duplicate tests
*/
//取得ep的传输类型
xfertype = usb_endpoint_type(&ep->desc);
//如果是控制传输.端点0默认是控制传输
if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
//控制传输的urb如果没有setup_packet是非法的
struct usb_ctrlrequest *setup =
(struct usb_ctrlrequest *) urb->setup_packet;
if (!setup)
return -ENOEXEC;
//判断是否是out方向的传输
is_out = !(setup->bRequestType & USB_DIR_IN) ||
!setup->wLength;
} else {
//如果不是控制传输,在端点描述符的bEndportAddress的bit7 包含有端点的传输方向
is_out = usb_endpoint_dir_out(&ep->desc);
}
/* Cache the direction for later use */
//根据传输方向.置urb->transfer_flags的方向位
urb->transfer_flags = (urb->transfer_flags & ~URB_DIR_MASK) |
(is_out ? URB_DIR_OUT : URB_DIR_IN);
//根据usb2.0 spec.除控制传输外的其它传输只有在config状态的时候才能进行
if (xfertype != USB_ENDPOINT_XFER_CONTROL &&
dev->state
return -ENODEV;
//传送/接收的最大字节.如果这个最大巧若拙字节还要小于0,那就是非法的
max = le16_to_cpu(ep->desc.wMaxPacketSize);
if (max
dev_dbg(&dev->dev,
'bogus endpoint ep%d%s in %s (bad maxpacket %d)n',
usb_endpoint_num(&ep->desc), is_out ? 'out' : 'in',
__FUNCTION__, max);
return -EMSGSIZE;
}
/* periodic transfers limit size per frame/uframe,
* but drivers only control those sizes for ISO.
* while we're checking, initialize return status.
*/
//如果是实时传输
if (xfertype == USB_ENDPOINT_XFER_ISOC) {
int n, len;
/* 'high bandwidth' mode, 1-3 packets/uframe? */
//如果是高速传输.则要修正它的MAX值
//高速传输时, 一个微帧内可以修输多个数据.bit 11~bit12用来表示一个微帧内
//传输包的个数.
//在USB1.1中是不支持HIGH的
if (dev->speed == USB_SPEED_HIGH) {
int mult = 1 + ((max >> 11) & 0x03);
max &= 0x07ff;
max *= mult;
}
//实现传输的数据包数目不能小于等于0
if (urb->number_of_packets
return -EINVAL;
//urb->number_of_packets: 实时数据包个数.每个实时数据包对应urb->iso_frame_desc[]中的一项
for (n = 0; n number_of_packets; n++) {
len = urb->iso_frame_desc[n].length;
if (len max)
return -EMSGSIZE;
urb->iso_frame_desc[n].status = -EXDEV;
urb->iso_frame_desc[n].actual_length = 0;
}
}
/* the I/O buffer must be mapped/unmapped, except when length=0 */
//如果要传输的缓存区大小小于0.非法
if (urb->transfer_buffer_length
return -EMSGSIZE;
#ifdef DEBUG
/* stuff that drivers shouldn't do, but which shouldn't
* cause problems in HCDs if they get it wrong.
*/
{
unsigned int orig_flags = urb->transfer_flags;
unsigned int allowed;
/* enforce simple/standard policy */
allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP |
URB_NO_INTERRUPT | URB_DIR_MASK | URB_FREE_BUFFER);
switch (xfertype) {
case USB_ENDPOINT_XFER_BULK:
if (is_out)
allowed |= URB_ZERO_PACKET;
/* FALLTHROUGH */
case USB_ENDPOINT_XFER_CONTROL:
allowed |= URB_NO_FSBR; /* only affects UHCI */
/* FALLTHROUGH */
default: /* all non-iso endpoints */
if (!is_out)
allowed |= URB_SHORT_NOT_OK;
break;
case USB_ENDPOINT_XFER_ISOC:
allowed |= URB_ISO_ASAP;
break;
}
urb->transfer_flags &= allowed;
/* fail if submitter gave bogus flags */
if (urb->transfer_flags != orig_flags) {
err('BOGUS urb flags, %x --> %x',
orig_flags, urb->transfer_flags);
return -EINVAL;
}
}
#endif
/*
* Force periodic transfer intervals to be legal values that are
* a power of two (so HCDs don't need to).
*
* FIXME want bus->{intr,iso}_sched_horizon values here. Each HC
* supports different values... this uses EHCI/UHCI defaults (and
* EHCI can use smaller non-default values).
*/
//关于实时传输和中断传输的interval处理
switch (xfertype) {
case USB_ENDPOINT_XFER_ISOC:
case USB_ENDPOINT_XFER_INT:
/* too small? */
//interval不能小于或等于0
if (urb->interval
return -EINVAL;
/* too big? */
switch (dev->speed) {
case USB_SPEED_HIGH: /* units are microframes */
/* NOTE usb handles 2^15 */
if (urb->interval > (1024 * 8))
urb->interval = 1024 * 8;
max = 1024 * 8;
break;
case USB_SPEED_FULL: /* units are frames/msec */
case USB_SPEED_LOW:
if (xfertype == USB_ENDPOINT_XFER_INT) {
if (urb->interval > 255)
return -EINVAL;
/* NOTE ohci only handles up to 32 */
max = 128;
} else {
if (urb->interval > 1024)
urb->interval = 1024;
/* NOTE usb and ohci handle up to 2^15 */
max = 1024;
}
break;
default:
return -EINVAL;
}
/* Round down to a power of 2, no more than max */
urb->interval = min(max, 1 interval));
}
return usb_hcd_submit_urb(urb, mem_flags);
}
这段代码虽然很长,但逻辑很清楚.对照代码中的注释理解应该是没有问题的.在这里要注意,UHCI是属于USB1.1的,它不支持HIGH传输.
对URB进行一系列处理之后,就会将urb丢给hcd进行处理了.usb_hcd_submit_urb()代码如下:
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
int status;
//从usb_bus的地址取得usb_hcd的地址
struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
/* increment urb's reference count as part of giving it to the HCD
* (which will control it). HCD guarantees that it either returns
* an error or calls giveback(), but not both.
*/
//增加有关的引用计数,usbmon*系列的函数是编译选择的.忽略
usb_get_urb(urb);
atomic_inc(&urb->use_count);
atomic_inc(&urb->dev->urbnum);
usbmon_urb_submit(&hcd->self, urb);
/* NOTE requirements on root-hub callers (usbfs and the hub
* driver, for now): URBs' urb->transfer_buffer must be
* valid and usb_buffer_{sync,unmap}() not be needed, since
* they could clobber root hub response data. Also, control
* URBs must be submitted in process context with interrupts
* enabled.
*/
//对传输的缓存区进行DMA映射
status = map_urb_for_dma(hcd, urb, mem_flags);
//出现错误,返回
if (unlikely(status)) {
usbmon_urb_submit_error(&hcd->self, urb, status);
goto error;
}
//如果是root hub
if (is_root_hub(urb->dev))
status = rh_urb_enqueue(hcd, urb);
else
//如果是一般的设备
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
if (unlikely(status)) {
usbmon_urb_submit_error(&hcd->self, urb, status);
unmap_urb_for_dma(hcd, urb);
error:
urb->hcpriv = NULL;
INIT_LIST_HEAD(&urb->urb_list);
atomic_dec(&urb->use_count);
atomic_dec(&urb->dev->urbnum);
if (urb->reject)
wake_up(&usb_kill_urb_queue);
usb_put_urb(urb);
}
return status;
}
在这里函数里要注意到,urb->transfer_buffer是一个虚拟地址,用于UHCI的时候,必须要将其映射物理地址,以供设备使用.这 也就是map_urb_for_dma()要完成的工作. map_urb_for_dma()函数比较简单,这里就不做详细分析.
可能有人会有这样的疑惑,对于root hub的情况,为什么不用对传输缓存区进行DMA映射呢?
在后面的处理中我们可以看到,其实对于root hub ,它不需要进行实际的物理传输,linux按照spec上的规定,将它静态放置在内存中,在进行相关操作的时候,只要直接copy过去就可以了.
其次,要注意,这个函数不能用于中断上下文,因为该函数是同步的,会引起睡眠.
在这里,我们看到,流程最终转入到了下面的代码片段中:
//如果是root hub
if (is_root_hub(urb->dev))
status = rh_urb_enqueue(hcd, urb);
else
//如果是一般的设备
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
下面,就分情况来剖析,各种传输到底是怎么完成的.
上一篇:USB设备驱动程序(二)
- 学习ARM开发(2)
- Linux帧缓冲设备驱动程序框架及图形界面GUI的移植
- Linux Kernel之flush_cache_all在ARM平台下是如何实现的
- 手把手教你写Linux设备驱动---中断(三)--workqueue实现(基于友善之臂4412开发板)
- makefile初步制作,arm-linux- (gcc/ld/objcopy/objdump)详解
- Ubuntu下安装arm-linux-gnueabi-xxx编译器
- 用Qemu运行/调试arm linux
- Linux内核异常处理体系结构详解(一)
- arm linux 移植 mtd-utils 1.x
- 基于gnu-arm-linux的LPC2220的简单工程模板