刚开始一直没有关心出错的地方是端点功能配置问题。
s3c_udc_otg.c中,共15个端点。除端点0外,
<pre name="code" class="cpp"><span style="font-family:SimSun;font-size:14px;"> .ep[1] = { .ep = { .name = "ep1-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_OUT | 1, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_out, .fifo = (unsigned int) S3C_UDC_OTG_EP1_FIFO, }, .ep[2] = { .ep = { .name = "ep2-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 2, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_in, .fifo = (unsigned int) S3C_UDC_OTG_EP2_FIFO, }, .ep[3] = { .ep = { .name = "ep3-int", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 3, .bmAttributes = USB_ENDPOINT_XFER_INT, .ep_type = ep_interrupt, .fifo = (unsigned int) S3C_UDC_OTG_EP3_FIFO, }, .ep[4] = { .ep = { .name = "ep4-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_OUT | 4, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_out, .fifo = (unsigned int) S3C_UDC_OTG_EP4_FIFO, }, .ep[5] = { .ep = { .name = "ep5-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 5, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_in, .fifo = (unsigned int) S3C_UDC_OTG_EP5_FIFO, }, .ep[6] = { .ep = { .name = "ep6-int", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 6, .bmAttributes = USB_ENDPOINT_XFER_INT, .ep_type = ep_interrupt, .fifo = (unsigned int) S3C_UDC_OTG_EP6_FIFO, }, .ep[7] = { .ep = { .name = "ep7-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_OUT | 7, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_out, .fifo = (unsigned int) S3C_UDC_OTG_EP7_FIFO, }, .ep[8] = { .ep = { .name = "ep8-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 8, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_in, .fifo = (unsigned int) S3C_UDC_OTG_EP8_FIFO, }, .ep[9] = { .ep = { .name = "ep9-int", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 9, .bmAttributes = USB_ENDPOINT_XFER_INT, .ep_type = ep_interrupt, .fifo = (unsigned int) S3C_UDC_OTG_EP9_FIFO, }, .ep[10] = { .ep = { .name = "ep10-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_OUT | 0xa, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_out, .fifo = (unsigned int) S3C_UDC_OTG_EP10_FIFO, }, .ep[11] = { .ep = { .name = "ep11-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 0xb, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_in, .fifo = (unsigned int) S3C_UDC_OTG_EP11_FIFO, }, .ep[12] = { .ep = { .name = "ep12-int", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 0xc, .bmAttributes = USB_ENDPOINT_XFER_INT, .ep_type = ep_interrupt, .fifo = (unsigned int) S3C_UDC_OTG_EP12_FIFO, }, .ep[13] = { .ep = { .name = "ep13-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_OUT | 0xd, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_out, .fifo = (unsigned int) S3C_UDC_OTG_EP13_FIFO, }, .ep[14] = { .ep = { .name = "ep14-bulk", .ops = &s3c_ep_ops, .maxpacket = EP_FIFO_SIZE, }, .dev = &memory, .bEndpointAddress = USB_DIR_IN | 0xe, .bmAttributes = USB_ENDPOINT_XFER_BULK, .ep_type = ep_bulk_in, .fifo = (unsigned int) S3C_UDC_OTG_EP14_FIFO, },</span>在usb_auto_config()中也归类出来了
<span style="font-family:SimSun;font-size:14px;">else if (gadget_is_s3c(gadget)) { if (USB_ENDPOINT_XFER_INT == type) { /* single buffering is enough */ ep = find_ep (gadget, "ep3-int"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep6-int"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep9-int"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep12-int"); if (ep && ep_matches (gadget, ep, desc)) return ep; } else if (USB_ENDPOINT_XFER_BULK == type && (USB_DIR_IN & desc->bEndpointAddress)) { ep = find_ep (gadget, "ep2-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep5-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep8-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep11-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep14-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; } else if (USB_ENDPOINT_XFER_BULK == type && !(USB_DIR_IN & desc->bEndpointAddress)) { ep = find_ep (gadget, "ep1-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep4-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep7-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep10-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; ep = find_ep (gadget, "ep13-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; } }</span>ep3/6/9/12定义是int-in, ep2/5/8/11/14定义是bulk-in,ep1/4/7/10/13定义是bulk-out。
“
端点:
端点位于USB 外设内部,所有通信数据的来源或目的都基于这些端点,是一个可寻址的FIFO。
每个USB 外设有一个唯一的地址,可能包含最多十六个端点。主机通过发出器件地址和每次数据传输的端点号,向一个具体端点(FIFO)发送数据。
每个端点的地址为0 到15,一个端点地址对应一个方向。所以,端点2-IN 与端点2-OUT 完全不同。 每个器件有一个默认的双向控制端点0,因此不存在端点0-IN 和端点0-OUT。
USB四种传输模式
控制传输、批量传输、中断传输、同步传输
USB 有上述四种传输类型。枚举期间外设告诉主机每个端点支持哪种传输类型。
USB设备驱动向USB控制器驱动请求的每次传输被称为一个事务(Transaction),
事务有四种类型:Bulk Transaction、Control Transaction、Interrupt Transaction和Isochronous Transaction。
数据包包含部分:
每次事务都会分解成若干个数据包在USB总线上传输。每次传输必须历经两个或三个部分,第一部分——USB控制器向USB设备发出命令,
第二部分——USB控制器和USB设备之间传递读写请求,其方向主要看第一部分的命令是读还是写,第二部分有时候可以没有。
第三部分——握手信号。
批量(Bulk)传输事务
作用:主要应用在数据大量数据传输和接受数据上同时又没有带宽和间隔时间要求的情况下;
特点:要求保证传输。打印机和扫描仪属于这种类型这种类型的设备
适合于传输非常慢和大量被延迟的传输,可以等到所有其它类型的数据的传输完成之后再传输和接收数据。
批量数据传输分三个阶段:
第一部分——令牌阶段。
Host端发出一个Bulk的令牌请求。
如果令牌是IN请求 ,则是从Device到Host的请求;
如果令牌是OUT请求,则是从Host到Device端的请求。
第二部分——传送数据的阶段。
根据先前请求的令牌的类型,数据传输有可能是IN方向,也有可能是OUT方向。传输数据的时候用DATA0和DATA1令牌携带着数据交替传送。
数据传输格式DATA1和DATA0,这两个是重复数据,确保在1数据丢失时0可以补上,不至于数据丢失。
第三部分——握手阶段。
如果数据是IN 方向,握手信号应该是Host端发出;
如果数据是OUT方向,握手信号应该是Device端发出。
握手信号可以为ACK, 表示正常响应,
NAK, 表示没有正确传送。
STALL,表示出现主机不可预知的错误。
控制(Control)传输
作用:USB系统软件用来主要进行查询配置和给USB设备发送通用的命令;
特点:控制传输是双向传输,数据量通常较小;数据传送是无损性的。
数据宽度:控制传输方式可以包括8、16、32和64字节的数据,这依赖于设备和传输速度。
控制传输典型地用在主计算机和USB外设之间的端点0(EP0)之间的传输
控制传输也分为三个阶段,即令牌阶段、数据传送阶段、握手阶段。
中断(Interrupt)传输事务
作用:主要用于定时查询设备是否有中断数据要传输;
特点:设备的端点模式器的结构决定了它的查询频率从1到255ms之间。
典型的应用在少量的分散的不可预测数据的传输键盘操纵杆和鼠标就属于这一类型
(数据量很小)
中断方式传输是单向的并且对于host 来说只有输入(IN)的方式
在中断事务中,也分为三个阶段,即令牌阶段、数据传输阶段、握手阶段。
同步(Isochronous)传输事务
作用:用于时间严格并具有较强容错性的流数据传输,或者用于要求恒定的数据传输率的即时应用中。例如执行即时通话的网络电话。
特点:保证传输的同步性。保证每秒有固定的传输量。
(与Bulk传输不同)同步传输允许有一定的误码率。(这样符合视频会议等传输的需求,因为视频会议首先要保证实时性,在一定条件下,允许有一定的误码率。)
同步传输事务有只有两个阶段,即令牌阶段、数据阶段,因为不关心数据的正确性,故没有握手阶段,
”
一个真实摄像头的端点:
Video Control
bNumEndpoints 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 16
Video Streaming
bNumEndpoints 0
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
Video Streaming
bNumEndpoints 1
除ep0外,就ep1和ep3,ep3是int-in,ep1是isoc-in。
s3c-udc中int-in有了,但isoc-in没有。查看了下s5pv210的手册,Isochronous模式是支持的。下面的问题是怎么配置了。
考虑将ep[14]修改为isoc-in。
由usb_ep_autoconfig调用的ep_matches()可知,对ep name做后缀检测。type-restriction: "-iso", "-bulk", or "-int".
direction-restriction: "in", "out". 因此
(1) 将name修改为“ep14-iso”
(2)bmAttributes修改为USB_ENDPOINT_XFER_ISOC
(3)ep_type修改为ep_isochronous
ep_type_t中添加enum值ep_isochronous
(4) 修改添加
“else if (USB_ENDPOINT_XFER_ISOC == type
&& (USB_DIR_IN & desc->bEndpointAddress)) {
ep = find_ep (gadget, "ep14-iso");
if (ep && ep_matches (gadget, ep, desc))
return ep;
}”
这样,将ep14修改为isoc-in了。
编译内核,重新烧写,加载g_webcam.ko。在其他usb host接口上没接usb摄像头时,没有什么生成。接上摄像头后,在/dev下多了一个节点。主设备号为81,由于次设备号为4,就自动生成了/dev/video4。
使用getframe看看。
getframe.c:
<span style="font-family:SimSun;font-size:14px;">#include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <linux/types.h> /* for videodev2.h */ #include <linux/videodev2.h> int xioctl(int fd, int IOCTL_X, void *arg) { int ret = 0; int tries = 4; do { ret = ioctl(fd, IOCTL_X, arg); } while(ret && tries-- && ((errno == EINTR) || (errno == EAGAIN) || (errno == ETIMEDOUT))); if(ret && (tries <= 0)) fprintf(stderr, "ioctl (%i) retried %i times - giving up: %s)\n", IOCTL_X, 4, strerror(errno)); return (ret); } int main(int argc, char *argv[]) { int fd = -1; struct v4l2_capability cap; int ret = 0; struct v4l2_format format; struct v4l2_requestbuffers rb; int i = 0; struct v4l2_buffer buf; void *mem[4]; int memlen[4]; unsigned char *tmpbuf; char *dev = "/dev/video0"; if(argc == 2) dev = argv[1]; fd = open(dev, O_RDWR); if(fd < 0) { perror("open"); return -1; } memset(&cap, 0, sizeof(struct v4l2_capability)); ret = xioctl(fd, VIDIOC_QUERYCAP, &cap); if(ret < 0) { fprintf(stderr, "Error opening device %s: unable to query device.\n", dev); goto end; } /* __u8 driver[16]; __u8 card[32]; __u8 bus_info[32]; __u32 version; //should use KERNEL_VERSION() __u32 capabilities; //Device capabilities */ fprintf(stderr, "driver: %s, card: %s, bus_info: %s, version: %x, capabilities: %x\n", cap.driver, cap.card, cap.bus_info, cap.version, cap.capabilities); if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { fprintf(stderr, "Error opening device %s: video capture not supported.\n", dev); goto end; } memset(&format, 0, sizeof(struct v4l2_format)); format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.fmt.pix.width = 640; format.fmt.pix.height = 480; format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; format.fmt.pix.field = V4L2_FIELD_ANY; ret = xioctl(fd, VIDIOC_S_FMT, &format); if(ret < 0) { fprintf(stderr, "Unable to set format: V4L2_PIX_FMT_MJPEG res: %dx%d\n", format.fmt.pix.width, format.fmt.pix.height); goto end; } /* * request buffers */ memset(&rb, 0, sizeof(struct v4l2_requestbuffers)); rb.count = 4; rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rb.memory = V4L2_MEMORY_MMAP; ret = xioctl(fd, VIDIOC_REQBUFS, &rb); if(ret < 0) { perror("Unable to allocate buffers"); goto end; } /* * map the buffers */ for(i = 0; i < 4; i++) { memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; ret = xioctl(fd, VIDIOC_QUERYBUF, &buf); if(ret < 0) { perror("Unable to query buffer"); goto end; } fprintf(stderr, "length: %u offset: %u\n", buf.length, buf.m.offset); mem[i] = mmap(0 /* start anywhere */ , buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if(mem[i] == MAP_FAILED) { perror("Unable to map buffer"); ret = -ENOMEM; goto end; } memlen[i] = buf.length; //fprintf(stderr, "Buffer mapped at address %p.\n", mem[i]); fprintf(stderr, "Buffer mapped\n"); } /* * Queue the buffers. */ for(i = 0; i < 4; ++i) { memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; ret = xioctl(fd, VIDIOC_QBUF, &buf); if(ret < 0) { perror("Unable to queue buffer"); goto end; } fprintf(stderr, "buf[%d] qbufed\n", i); } i = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == ioctl(fd, VIDIOC_STREAMON, &i)) { fprintf(stderr, "VIDIOC_STREAMON failed\n"); ret = -1; goto unmap; } memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; ret = xioctl(fd, VIDIOC_DQBUF, &buf); if(ret < 0) { perror("Unable to dequeue buffer"); goto end; } tmpbuf = mem[buf.index]; fprintf(stderr, "mem: "); for(i=0; i<10; i++) fprintf(stderr, "%02x ", tmpbuf[i]); fprintf(stderr, "\n"); fprintf(stderr, "bytes in used %d \n", buf.bytesused); i = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &i)) { fprintf(stderr, "VIDIOC_STREAMOFF failed\n"); ret = -1; } else ret = 0; unmap: for(i=0; i<4; ++i) if (-1 == munmap(mem[i], memlen[i])) printf ("munmap buf[%d] error\n", i); end: close(fd); return ret; }</span>这个程序一开始是获取一帧图像数据,后来改成查看一帧(MPJEG输出)数据的头几个字节。运行提示“ video capture not supported ”。就添加了打印。
搜索可知,在drivers/usb/gadget/uvc_v4l2.c中uvc_v4l2_do_ioctl()。
/* Query capabilities */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap, 0, sizeof *cap);
strncpy(cap->driver, "g_uvc", sizeof(cap->driver));
strncpy(cap->card, cdev->gadget->name, sizeof(cap->card));
strncpy(cap->bus_info, dev_name(&cdev->gadget->dev),
sizeof cap->bus_info);
cap->version = DRIVER_VERSION_NUMBER;
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
break;
}
设置了V4L2_CAP_VIDEO_OUTPUT, 0x4000002 = 0x2 + 0x4000000
V4L2_CAP_VIDEO_OUTPUT是什么不太清楚,网上搜到了《V4L2 Video overlay, Video output, Video output overlay的区别 》
“
Video Output:
Video output devices encode stills or image sequences as analog video signal.
按照V4L2的spec,Video output设备是把静态图片编码为模拟video信号,这就意味着output 设备的输出是模拟video信号
对于output device输出是模拟信号,我们可以从http://v4l2spec.bytesex.org/spec-single/v4l2.html#VIDIOC-ENUMOUTPUT 的output type定义找到侧证。
数据修改:通过设备节点/dev/videox的read/write功能,以及stream的内存映射方式修改
备注:四种analog video信号分别为CVBS, S-Video, YPbPr, RGB
”
弄了半天,原来g_webcam是做成模拟(信号)摄像头的。
看来还得自己按照这个框架写一个。但“要怎么使用,还得怎么做”,这些还得继续探索。