📄 pwc-if.c
字号:
Yes, this is a mess. Well, it used to be simple, but alas... In this module, 3 buffers schemes are used to get the data from the USB bus to the user program. The first scheme involves the ISO buffers (called thus since they transport ISO data from the USB controller), and not really interesting. Suffices to say the data from this buffer is quickly gathered in an interrupt handler (pwc_isoc_handler) and placed into the frame buffer. The frame buffer is the second scheme, and is the central element here. It collects the data from a single frame from the camera (hence, the name). Frames are delimited by the USB camera with a short USB packet, so that's easy to detect. The frame buffers form a list that is filled by the camera+USB controller and drained by the user process through either read() or mmap(). The image buffer is the third scheme, in which frames are decompressed and converted into planar format. For mmap() there is more than one image buffer available. The frame buffers provide the image buffering. In case the user process is a bit slow, this introduces lag and some undesired side-effects. The problem arises when the frame buffer is full. I used to drop the last frame, which makes the data in the queue stale very quickly. But dropping the frame at the head of the queue proved to be a litte bit more difficult. I tried a circular linked scheme, but this introduced more problems than it solved. Because filling and draining are completely asynchronous processes, this requires some fiddling with pointers and mutexes. Eventually, I came up with a system with 2 lists: an 'empty' frame list and a 'full' frame list: * Initially, all frame buffers but one are on the 'empty' list; the one remaining buffer is our initial fill frame. * If a frame is needed for filling, we try to take it from the 'empty' list, unless that list is empty, in which case we take the buffer at the head of the 'full' list. * When our fill buffer has been filled, it is appended to the 'full' list. * If a frame is needed by read() or mmap(), it is taken from the head of the 'full' list, handled, and then appended to the 'empty' list. If no buffer is present on the 'full' list, we wait. The advantage is that the buffer that is currently being decompressed/ converted, is on neither list, and thus not in our way (any other scheme I tried had the problem of old data lingering in the queue). Whatever strategy you choose, it always remains a tradeoff: with more frame buffers the chances of a missed frame are reduced. On the other hand, on slower machines it introduces lag because the queue will always be full. *//** \brief Find next frame buffer to fill. Take from empty or full list, whichever comes first. */static int pwc_next_fill_frame(struct pwc_device *pdev){ int ret; unsigned long flags; ret = 0; spin_lock_irqsave(&pdev->ptrlock, flags); if (pdev->fill_frame != NULL) { /* append to 'full' list */ if (pdev->full_frames == NULL) { pdev->full_frames = pdev->fill_frame; pdev->full_frames_tail = pdev->full_frames; } else { pdev->full_frames_tail->next = pdev->fill_frame; pdev->full_frames_tail = pdev->fill_frame; } } if (pdev->empty_frames != NULL) { /* We have empty frames available. That's easy */ pdev->fill_frame = pdev->empty_frames; pdev->empty_frames = pdev->empty_frames->next; } else { /* Hmm. Take it from the full list */ /* sanity check */ if (pdev->full_frames == NULL) { PWC_ERROR("Neither empty or full frames available!\n"); spin_unlock_irqrestore(&pdev->ptrlock, flags); return -EINVAL; } pdev->fill_frame = pdev->full_frames; pdev->full_frames = pdev->full_frames->next; ret = 1; } pdev->fill_frame->next = NULL; spin_unlock_irqrestore(&pdev->ptrlock, flags); return ret;}/** \brief Reset all buffers, pointers and lists, except for the image_used[] buffer. If the image_used[] buffer is cleared too, mmap()/VIDIOCSYNC will run into trouble. */static void pwc_reset_buffers(struct pwc_device *pdev){ int i; unsigned long flags; PWC_DEBUG_MEMORY(">> %s __enter__\n", __FUNCTION__); spin_lock_irqsave(&pdev->ptrlock, flags); pdev->full_frames = NULL; pdev->full_frames_tail = NULL; for (i = 0; i < default_fbufs; i++) { pdev->fbuf[i].filled = 0; if (i > 0) pdev->fbuf[i].next = &pdev->fbuf[i - 1]; else pdev->fbuf->next = NULL; } pdev->empty_frames = &pdev->fbuf[default_fbufs - 1]; pdev->empty_frames_tail = pdev->fbuf; pdev->read_frame = NULL; pdev->fill_frame = pdev->empty_frames; pdev->empty_frames = pdev->empty_frames->next; pdev->image_read_pos = 0; pdev->fill_image = 0; spin_unlock_irqrestore(&pdev->ptrlock, flags); PWC_DEBUG_MEMORY("<< %s __leaving__\n", __FUNCTION__);}/** \brief Do all the handling for getting one frame: get pointer, decompress, advance pointers. */int pwc_handle_frame(struct pwc_device *pdev){ int ret = 0; unsigned long flags; spin_lock_irqsave(&pdev->ptrlock, flags); /* First grab our read_frame; this is removed from all lists, so we can release the lock after this without problems */ if (pdev->read_frame != NULL) { /* This can't theoretically happen */ PWC_ERROR("Huh? Read frame still in use?\n"); spin_unlock_irqrestore(&pdev->ptrlock, flags); return ret; } if (pdev->full_frames == NULL) { PWC_ERROR("Woops. No frames ready.\n"); } else { pdev->read_frame = pdev->full_frames; pdev->full_frames = pdev->full_frames->next; pdev->read_frame->next = NULL; } if (pdev->read_frame != NULL) { /* Decompression is a lenghty process, so it's outside of the lock. This gives the isoc_handler the opportunity to fill more frames in the mean time. */ spin_unlock_irqrestore(&pdev->ptrlock, flags); ret = pwc_decompress(pdev); spin_lock_irqsave(&pdev->ptrlock, flags); /* We're done with read_buffer, tack it to the end of the empty buffer list */ if (pdev->empty_frames == NULL) { pdev->empty_frames = pdev->read_frame; pdev->empty_frames_tail = pdev->empty_frames; } else { pdev->empty_frames_tail->next = pdev->read_frame; pdev->empty_frames_tail = pdev->read_frame; } pdev->read_frame = NULL; } spin_unlock_irqrestore(&pdev->ptrlock, flags); return ret;}/** \brief Advance pointers of image buffer (after each user request)*/void pwc_next_image(struct pwc_device *pdev){ pdev->image_used[pdev->fill_image] = 0; pdev->fill_image = (pdev->fill_image + 1) % pwc_mbufs;}/** * Print debug information when a frame is discarded because all of our buffer * is full */static void pwc_frame_dumped(struct pwc_device *pdev){ pdev->vframes_dumped++; if (pdev->vframe_count < FRAME_LOWMARK) return; if (pdev->vframes_dumped < 20) PWC_DEBUG_FLOW("Dumping frame %d\n", pdev->vframe_count); else if (pdev->vframes_dumped == 20) PWC_DEBUG_FLOW("Dumping frame %d (last message)\n", pdev->vframe_count);}static int pwc_rcv_short_packet(struct pwc_device *pdev, const struct pwc_frame_buf *fbuf){ int awake = 0; /* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus frames on the USB wire after an exposure change. This conditition is however detected in the cam and a bit is set in the header. */ if (pdev->type == 730) { unsigned char *ptr = (unsigned char *)fbuf->data; if (ptr[1] == 1 && ptr[0] & 0x10) { PWC_TRACE("Hyundai CMOS sensor bug. Dropping frame.\n"); pdev->drop_frames += 2; pdev->vframes_error++; } if ((ptr[0] ^ pdev->vmirror) & 0x01) { if (ptr[0] & 0x01) { pdev->snapshot_button_status = 1; PWC_TRACE("Snapshot button pressed.\n"); } else { PWC_TRACE("Snapshot button released.\n"); } } if ((ptr[0] ^ pdev->vmirror) & 0x02) { if (ptr[0] & 0x02) PWC_TRACE("Image is mirrored.\n"); else PWC_TRACE("Image is normal.\n"); } pdev->vmirror = ptr[0] & 0x03; /* Sometimes the trailer of the 730 is still sent as a 4 byte packet after a short frame; this condition is filtered out specifically. A 4 byte frame doesn't make sense anyway. So we get either this sequence: drop_bit set -> 4 byte frame -> short frame -> good frame Or this one: drop_bit set -> short frame -> good frame So we drop either 3 or 2 frames in all! */ if (fbuf->filled == 4) pdev->drop_frames++; } else if (pdev->type == 740 || pdev->type == 720) { unsigned char *ptr = (unsigned char *)fbuf->data; if ((ptr[0] ^ pdev->vmirror) & 0x01) { if (ptr[0] & 0x01) { pdev->snapshot_button_status = 1; PWC_TRACE("Snapshot button pressed.\n"); } else PWC_TRACE("Snapshot button released.\n"); } pdev->vmirror = ptr[0] & 0x03; } /* In case we were instructed to drop the frame, do so silently. The buffer pointers are not updated either (but the counters are reset below). */ if (pdev->drop_frames > 0) pdev->drop_frames--; else { /* Check for underflow first */ if (fbuf->filled < pdev->frame_total_size) { PWC_DEBUG_FLOW("Frame buffer underflow (%d bytes);" " discarded.\n", fbuf->filled); pdev->vframes_error++; } else { /* Send only once per EOF */ awake = 1; /* delay wake_ups */ /* Find our next frame to fill. This will always succeed, since we * nick a frame from either empty or full list, but if we had to * take it from the full list, it means a frame got dropped. */ if (pwc_next_fill_frame(pdev)) pwc_frame_dumped(pdev); } } /* !drop_frames */ pdev->vframe_count++; return awake;}/* This gets called for the Isochronous pipe (video). This is done in * interrupt time, so it has to be fast, not crash, and not stall. Neat. */static void pwc_isoc_handler(struct urb *urb, struct pt_regs *regs){ struct pwc_device *pdev; int i, fst, flen; int awake; struct pwc_frame_buf *fbuf; unsigned char *fillptr = NULL, *iso_buf = NULL; awake = 0; pdev = (struct pwc_device *)urb->context; if (pdev == NULL) { PWC_ERROR("isoc_handler() called with NULL device?!\n"); return; } if (urb->status == -ENOENT || urb->status == -ECONNRESET) { PWC_DEBUG_OPEN("URB (%p) unlinked %ssynchronuously.\n", urb, urb->status == -ENOENT ? "" : "a"); return; } if (urb->status != -EINPROGRESS && urb->status != 0) { const char *errmsg; errmsg = "Unknown"; switch(urb->status) { case -ENOSR: errmsg = "Buffer error (overrun)"; break; case -EPIPE: errmsg = "Stalled (device not responding)"; break; case -EOVERFLOW: errmsg = "Babble (bad cable?)"; break; case -EPROTO: errmsg = "Bit-stuff error (bad cable?)"; break; case -EILSEQ: errmsg = "CRC/Timeout (could be anything)"; break; case -ETIMEDOUT: errmsg = "NAK (device does not respond)"; break; } PWC_DEBUG_FLOW("pwc_isoc_handler() called with status %d [%s].\n", urb->status, errmsg); /* Give up after a number of contiguous errors on the USB bus. Appearantly something is wrong so we simulate an unplug event. */ if (++pdev->visoc_errors > MAX_ISOC_ERRORS) { PWC_INFO("Too many ISOC errors, bailing out.\n"); pdev->error_status = EIO; awake = 1; wake_up_interruptible(&pdev->frameq); } goto handler_end; // ugly, but practical } fbuf = pdev->fill_frame; if (fbuf == NULL) { PWC_ERROR("pwc_isoc_handler without valid fill frame.\n"); awake = 1; goto handler_end; } else { fillptr = fbuf->data + fbuf->filled; } /* Reset ISOC error counter. We did get here, after all. */ pdev->visoc_errors = 0; /* vsync: 0 = don't copy data 1 = sync-hunt 2 = synched */ /* Compact data */ for (i = 0; i < urb->number_of_packets; i++) { fst = urb->iso_frame_desc[i].status; flen = urb->iso_frame_desc[i].actual_length; iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset; if (fst == 0) { if (flen > 0) { /* if valid data... */ if (pdev->vsync > 0) { /* ...and we are not sync-hunting... */ pdev->vsync = 2; /* ...copy data to frame buffer, if possible */ if (flen + fbuf->filled > pdev->frame_total_size) { PWC_DEBUG_FLOW("Frame buffer overflow (flen = %d, frame_total_size = %d).\n", flen, pdev->frame_total_size); pdev->vsync = 0; /* Hmm, let's wait for an EOF (end-of-frame) */ pdev->vframes_error++; } else { memmove(fillptr, iso_buf, flen); fillptr += flen; } } fbuf->filled += flen; } /* ..flen > 0 */ if (flen < pdev->vlast_packet_size) { /* Shorter packet... We probably have the end of an image-frame; wake up read() process and let select()/poll() do something. Decompression is done in user time over there. */ if (pdev->vsync == 2) { if (pwc_rcv_short_packet(pdev, fbuf)) { awake = 1; fbuf = pdev->fill_frame; } } fbuf->filled = 0; fillptr = fbuf->data; pdev->vsync = 1; } pdev->vlast_packet_size = flen; } /* ..status == 0 */ else { /* This is normally not interesting to the user, unless * you are really debugging something */ static int iso_error = 0; iso_error++;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -