📄 gspca.c
字号:
/* * Main USB camera driver * * V4L2 by Jean-Francois Moine <http://moinejf.free.fr> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */#define MODULE_NAME "gspca"#include <linux/init.h>#include <linux/version.h>#include <linux/fs.h>#include <linux/vmalloc.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/mm.h>#include <linux/string.h>#include <linux/pagemap.h>#include <linux/io.h>#include <linux/kref.h>#include <asm/page.h>#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)#include <asm/uaccess.h>#else#include <linux/uaccess.h>#endif#include <linux/jiffies.h>#include <media/v4l2-ioctl.h>#include "gspca.h"/* global values */#define DEF_NURBS 2 /* default number of URBs */MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");MODULE_DESCRIPTION("GSPCA USB Camera Driver");MODULE_LICENSE("GPL");#define DRIVER_VERSION_NUMBER KERNEL_VERSION(2, 3, 0)static int video_nr = -1;#ifdef GSPCA_DEBUGint gspca_debug = D_ERR | D_PROBE;EXPORT_SYMBOL(gspca_debug);static void PDEBUG_MODE(char *txt, __u32 pixfmt, int w, int h){ if ((pixfmt >> 24) >= '0' && (pixfmt >> 24) <= 'z') { PDEBUG(D_CONF|D_STREAM, "%s %c%c%c%c %dx%d", txt, pixfmt & 0xff, (pixfmt >> 8) & 0xff, (pixfmt >> 16) & 0xff, pixfmt >> 24, w, h); } else { PDEBUG(D_CONF|D_STREAM, "%s 0x%08x %dx%d", txt, pixfmt, w, h); }}#else#define PDEBUG_MODE(txt, pixfmt, w, h)#endif/* specific memory types - !! should different from V4L2_MEMORY_xxx */#define GSPCA_MEMORY_NO 0 /* V4L2_MEMORY_xxx starts from 1 */#define GSPCA_MEMORY_READ 7#define BUF_ALL_FLAGS (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)/* * VMA operations. */static void gspca_vm_open(struct vm_area_struct *vma){ struct gspca_frame *frame = vma->vm_private_data; frame->vma_use_count++; frame->v4l2_buf.flags |= V4L2_BUF_FLAG_MAPPED;}static void gspca_vm_close(struct vm_area_struct *vma){ struct gspca_frame *frame = vma->vm_private_data; if (--frame->vma_use_count <= 0) frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_MAPPED;}static struct vm_operations_struct gspca_vm_ops = { .open = gspca_vm_open, .close = gspca_vm_close,};/* get the current input frame buffer */struct gspca_frame *gspca_get_i_frame(struct gspca_dev *gspca_dev){ struct gspca_frame *frame; int i; i = gspca_dev->fr_i; i = gspca_dev->fr_queue[i]; frame = &gspca_dev->frame[i]; if ((frame->v4l2_buf.flags & BUF_ALL_FLAGS) != V4L2_BUF_FLAG_QUEUED) return NULL; return frame;}EXPORT_SYMBOL(gspca_get_i_frame);/* * fill a video frame from an URB and resubmit */static void fill_frame(struct gspca_dev *gspca_dev, struct urb *urb){ struct gspca_frame *frame; __u8 *data; /* address of data in the iso message */ int i, len, st; cam_pkt_op pkt_scan; if (urb->status != 0) {#ifdef CONFIG_PM if (!gspca_dev->frozen)#endif PDEBUG(D_ERR|D_PACK, "urb status: %d", urb->status); return; /* disconnection ? */ } pkt_scan = gspca_dev->sd_desc->pkt_scan; for (i = 0; i < urb->number_of_packets; i++) { /* check the availability of the frame buffer */ frame = gspca_get_i_frame(gspca_dev); if (!frame) { gspca_dev->last_packet_type = DISCARD_PACKET; break; } /* check the packet status and length */ len = urb->iso_frame_desc[i].actual_length; if (len == 0) continue; st = urb->iso_frame_desc[i].status; if (st) { PDEBUG(D_ERR, "ISOC data error: [%d] len=%d, status=%d", i, len, st); gspca_dev->last_packet_type = DISCARD_PACKET; continue; } /* let the packet be analyzed by the subdriver */ PDEBUG(D_PACK, "packet [%d] o:%d l:%d", i, urb->iso_frame_desc[i].offset, len); data = (__u8 *) urb->transfer_buffer + urb->iso_frame_desc[i].offset; pkt_scan(gspca_dev, frame, data, len); } /* resubmit the URB */ urb->status = 0; st = usb_submit_urb(urb, GFP_ATOMIC); if (st < 0) PDEBUG(D_ERR|D_PACK, "usb_submit_urb() ret %d", st);}/* * ISOC message interrupt from the USB device * * Analyse each packet and call the subdriver for copy to the frame buffer. */static void isoc_irq(struct urb *urb#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) , struct pt_regs *regs#endif){ struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context; PDEBUG(D_PACK, "isoc irq"); if (!gspca_dev->streaming) return; fill_frame(gspca_dev, urb);}/* * bulk message interrupt from the USB device */static void bulk_irq(struct urb *urb#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) , struct pt_regs *regs#endif){ struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context; struct gspca_frame *frame; PDEBUG(D_PACK, "bulk irq"); if (!gspca_dev->streaming) return; if (urb->status != 0 && urb->status != -ECONNRESET) {#ifdef CONFIG_PM if (!gspca_dev->frozen)#endif PDEBUG(D_ERR|D_PACK, "urb status: %d", urb->status); return; /* disconnection ? */ } /* check the availability of the frame buffer */ frame = gspca_get_i_frame(gspca_dev); if (!frame) { gspca_dev->last_packet_type = DISCARD_PACKET; } else { PDEBUG(D_PACK, "packet l:%d", urb->actual_length); gspca_dev->sd_desc->pkt_scan(gspca_dev, frame, urb->transfer_buffer, urb->actual_length); }}/* * add data to the current frame * * This function is called by the subdrivers at interrupt level. * * To build a frame, these ones must add * - one FIRST_PACKET * - 0 or many INTER_PACKETs * - one LAST_PACKET * DISCARD_PACKET invalidates the whole frame. * On LAST_PACKET, a new frame is returned. */struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev, enum gspca_packet_type packet_type, struct gspca_frame *frame, const __u8 *data, int len){ int i, j; PDEBUG(D_PACK, "add t:%d l:%d", packet_type, len); /* when start of a new frame, if the current frame buffer * is not queued, discard the whole frame */ if (packet_type == FIRST_PACKET) { if ((frame->v4l2_buf.flags & BUF_ALL_FLAGS) != V4L2_BUF_FLAG_QUEUED) { gspca_dev->last_packet_type = DISCARD_PACKET; return frame; } frame->data_end = frame->data; jiffies_to_timeval(get_jiffies_64(), &frame->v4l2_buf.timestamp); frame->v4l2_buf.sequence = ++gspca_dev->sequence; } else if (gspca_dev->last_packet_type == DISCARD_PACKET) { if (packet_type == LAST_PACKET) gspca_dev->last_packet_type = packet_type; return frame; } /* append the packet to the frame buffer */ if (len > 0) { if (frame->data_end - frame->data + len > frame->v4l2_buf.length) { PDEBUG(D_ERR|D_PACK, "frame overflow %zd > %d", frame->data_end - frame->data + len, frame->v4l2_buf.length); packet_type = DISCARD_PACKET; } else { memcpy(frame->data_end, data, len); frame->data_end += len; } } gspca_dev->last_packet_type = packet_type; /* if last packet, wake up the application and advance in the queue */ if (packet_type == LAST_PACKET) { frame->v4l2_buf.bytesused = frame->data_end - frame->data; frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED; frame->v4l2_buf.flags |= V4L2_BUF_FLAG_DONE; atomic_inc(&gspca_dev->nevent); wake_up_interruptible(&gspca_dev->wq); /* event = new frame */ i = (gspca_dev->fr_i + 1) % gspca_dev->nframes; gspca_dev->fr_i = i; PDEBUG(D_FRAM, "frame complete len:%d q:%d i:%d o:%d", frame->v4l2_buf.bytesused, gspca_dev->fr_q, i, gspca_dev->fr_o); j = gspca_dev->fr_queue[i]; frame = &gspca_dev->frame[j]; } return frame;}EXPORT_SYMBOL(gspca_frame_add);static int gspca_is_compressed(__u32 format){ switch (format) { case V4L2_PIX_FMT_MJPEG: case V4L2_PIX_FMT_JPEG: case V4L2_PIX_FMT_SPCA561: case V4L2_PIX_FMT_PAC207: return 1; } return 0;}static void *rvmalloc(unsigned long size){ void *mem; unsigned long adr; mem = vmalloc_32(size); if (mem != NULL) { adr = (unsigned long) mem; while ((long) size > 0) { SetPageReserved(vmalloc_to_page((void *) adr)); adr += PAGE_SIZE; size -= PAGE_SIZE; } } return mem;}static void rvfree(void *mem, long size){ unsigned long adr; adr = (unsigned long) mem; while (size > 0) { ClearPageReserved(vmalloc_to_page((void *) adr)); adr += PAGE_SIZE; size -= PAGE_SIZE; } vfree(mem);}static int frame_alloc(struct gspca_dev *gspca_dev, unsigned int count){ struct gspca_frame *frame; unsigned int frsz; int i; i = gspca_dev->curr_mode; frsz = gspca_dev->cam.cam_mode[i].sizeimage; PDEBUG(D_STREAM, "frame alloc frsz: %d", frsz); frsz = PAGE_ALIGN(frsz); gspca_dev->frsz = frsz; if (count > GSPCA_MAX_FRAMES) count = GSPCA_MAX_FRAMES; gspca_dev->frbuf = rvmalloc(frsz * count); if (!gspca_dev->frbuf) { err("frame alloc failed"); return -ENOMEM; } gspca_dev->nframes = count; for (i = 0; i < count; i++) { frame = &gspca_dev->frame[i]; frame->v4l2_buf.index = i; frame->v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; frame->v4l2_buf.flags = 0; frame->v4l2_buf.field = V4L2_FIELD_NONE; frame->v4l2_buf.length = frsz; frame->v4l2_buf.memory = gspca_dev->memory; frame->v4l2_buf.sequence = 0; frame->data = frame->data_end = gspca_dev->frbuf + i * frsz; frame->v4l2_buf.m.offset = i * frsz; } gspca_dev->fr_i = gspca_dev->fr_o = gspca_dev->fr_q = 0; gspca_dev->last_packet_type = DISCARD_PACKET; gspca_dev->sequence = 0; atomic_set(&gspca_dev->nevent, 0); return 0;}static void frame_free(struct gspca_dev *gspca_dev){ int i; PDEBUG(D_STREAM, "frame free"); if (gspca_dev->frbuf != NULL) { rvfree(gspca_dev->frbuf, gspca_dev->nframes * gspca_dev->frsz); gspca_dev->frbuf = NULL; for (i = 0; i < gspca_dev->nframes; i++) gspca_dev->frame[i].data = NULL; } gspca_dev->nframes = 0;}static void destroy_urbs(struct gspca_dev *gspca_dev){ struct urb *urb; unsigned int i; PDEBUG(D_STREAM, "kill transfer"); for (i = 0; i < MAX_NURBS; i++) { urb = gspca_dev->urb[i]; if (urb == NULL) break; gspca_dev->urb[i] = NULL; usb_kill_urb(urb); if (urb->transfer_buffer != NULL) usb_buffer_free(gspca_dev->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); usb_free_urb(urb); }}/* * look for an input transfer endpoint in an alternate setting */static struct usb_host_endpoint *alt_xfer(struct usb_host_interface *alt, __u8 epaddr, __u8 xfer){ struct usb_host_endpoint *ep; int i, attr; epaddr |= USB_DIR_IN; for (i = 0; i < alt->desc.bNumEndpoints; i++) { ep = &alt->endpoint[i]; if (ep->desc.bEndpointAddress == epaddr) { attr = ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; if (attr == xfer) return ep; break; } } return NULL;}/* * look for an input (isoc or bulk) endpoint * * The endpoint is defined by the subdriver. * Use only the first isoc (some Zoran - 0x0572:0x0001 - have two such ep). * This routine may be called many times when the bandwidth is too small * (the bandwidth is checked on urb submit). */static struct usb_host_endpoint *get_ep(struct gspca_dev *gspca_dev){ struct usb_interface *intf; struct usb_host_endpoint *ep; int i, ret; intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface); ep = NULL; i = gspca_dev->alt; /* previous alt setting */ /* try isoc */ while (--i > 0) { /* alt 0 is unusable */ ep = alt_xfer(&intf->altsetting[i], gspca_dev->cam.epaddr, USB_ENDPOINT_XFER_ISOC); if (ep) break; } /* if no isoc, try bulk */ if (ep == NULL) { ep = alt_xfer(&intf->altsetting[0], gspca_dev->cam.epaddr, USB_ENDPOINT_XFER_BULK); if (ep == NULL) { err("no transfer endpoint found"); return NULL; } } PDEBUG(D_STREAM, "use alt %d ep 0x%02x", i, ep->desc.bEndpointAddress); if (i > 0) { ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, i); if (ret < 0) { err("set interface err %d", ret); return NULL; } } gspca_dev->alt = i; /* memorize the current alt setting */ return ep;}/* * create the URBs for image transfer */static int create_urbs(struct gspca_dev *gspca_dev, struct usb_host_endpoint *ep){ struct urb *urb; int n, nurbs, i, psize, npkt, bsize; /* calculate the packet size and the number of packets */ psize = le16_to_cpu(ep->desc.wMaxPacketSize);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -