patch-2.4.23 linux-2.4.23/drivers/usb/pwc-if.c

Next file: linux-2.4.23/drivers/usb/pwc-ioctl.h
Previous file: linux-2.4.23/drivers/usb/pwc-ctrl.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.22/drivers/usb/pwc-if.c linux-2.4.23/drivers/usb/pwc-if.c
@@ -1,6 +1,6 @@
-/* Linux driver for Philips webcam 
+/* Linux driver for Philips webcam
    USB and Video4Linux interface part.
-   (C) 1999-2002 Nemosoft Unv.
+   (C) 1999-2003 Nemosoft Unv.
 
    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
@@ -75,14 +75,21 @@
 	{ USB_DEVICE(0x0471, 0x0310) },
 	{ USB_DEVICE(0x0471, 0x0311) },
 	{ USB_DEVICE(0x0471, 0x0312) },
+	{ USB_DEVICE(0x0471, 0x0313) }, /* the 'new' 720K */
 	{ USB_DEVICE(0x069A, 0x0001) }, /* Askey */
-	{ USB_DEVICE(0x046D, 0x08b0) }, /* Logitech QuickCam Pro 3000 */
-	{ USB_DEVICE(0x046D, 0x08b1) }, /* Logitech QuickCam Notebook Pro */
-	{ USB_DEVICE(0x046d, 0x08b2) }, /* Logitech QuickCam Pro 4000 */
-	{ USB_DEVICE(0x046d, 0x08b3) }, /* Logitech QuickCam Zoom */
+	{ USB_DEVICE(0x046D, 0x08B0) }, /* Logitech QuickCam Pro 3000 */
+	{ USB_DEVICE(0x046D, 0x08B1) }, /* Logitech QuickCam Notebook Pro */
+	{ USB_DEVICE(0x046D, 0x08B2) }, /* Logitech QuickCam Pro 4000 */
+	{ USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom */
+	{ USB_DEVICE(0x046D, 0x08B4) }, /* Logitech (reserved) */
+	{ USB_DEVICE(0x046D, 0x08B5) }, /* Logitech (reserved) */
+	{ USB_DEVICE(0x046D, 0x08B6) }, /* Logitech (reserved) */
+	{ USB_DEVICE(0x046D, 0x08B7) }, /* Logitech (reserved) */
+	{ USB_DEVICE(0x046D, 0x08B8) }, /* Logitech (reserved) */
 	{ USB_DEVICE(0x055D, 0x9000) }, /* Samsung */
 	{ USB_DEVICE(0x055D, 0x9001) },
 	{ USB_DEVICE(0x041E, 0x400C) }, /* Creative Webcam 5 */
+	{ USB_DEVICE(0x041E, 0x4011) }, /* Creative Webcam Pro Ex */
 	{ USB_DEVICE(0x04CC, 0x8116) }, /* Afina Eye */
 	{ USB_DEVICE(0x0d81, 0x1910) }, /* Visionite */
 	{ USB_DEVICE(0x0d81, 0x1900) },
@@ -101,11 +108,11 @@
 	disconnect:		usb_pwc_disconnect,	/* disconnect() */
 };
 
-#define MAX_DEV_HINTS 10
+#define MAX_DEV_HINTS	20
+#define MAX_ISOC_ERRORS	20
 
 static int default_size = PSZ_QCIF;
 static int default_fps = 10;
-static int default_palette = VIDEO_PALETTE_YUV420P; /* This format is understood by most tools */
 static int default_fbufs = 3;   /* Default number of frame buffers */
 static int default_mbufs = 2;	/* Default number of mmap() buffers */
        int pwc_trace = TRACE_MODULE | TRACE_FLOW | TRACE_PWCX;
@@ -119,9 +126,6 @@
 	struct pwc_device *pdev;
 } device_hint[MAX_DEV_HINTS];
 
-static struct semaphore mem_lock;
-static void *mem_leak = NULL; /* For delayed kfree()s. See below */
-
 /***/
 
 static int  pwc_video_open(struct video_device *vdev, int mode);
@@ -383,50 +387,50 @@
    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 
+   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 
+   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 possibly converted into planar format. For mmap() there is more than
+   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 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 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' 
+     * 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 
+     * 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 
+   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 
+   hand, on slower machines it introduces lag because the queue will
    always be full.
  */
 
@@ -437,7 +441,7 @@
 {
 	int ret;
 	unsigned long flags;
-	
+
 	ret = 0;
 	spin_lock_irqsave(&pdev->ptrlock, flags);
 	if (pdev->fill_frame != NULL) {
@@ -454,11 +458,11 @@
 	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;	
+		pdev->empty_frames = pdev->empty_frames->next;
 	}
 	else {
 		/* Hmm. Take it from the full list */
-#if PWC_DEBUG		
+#if PWC_DEBUG
 		/* sanity check */
 		if (pdev->full_frames == NULL) {
 			Err("Neither empty or full frames available!\n");
@@ -478,11 +482,11 @@
 	spin_unlock_irqrestore(&pdev->ptrlock, flags);
 	return ret;
 }
- 
+
 
 /**
-  \brief Reset all buffers, pointers and lists, except for the image_used[] buffer. 
-  
+  \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)
@@ -519,7 +523,7 @@
 {
 	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 */
@@ -542,7 +546,7 @@
 			Trace(TRACE_SEQUENCE, "Decompressing frame %d\n", pdev->read_frame->sequence);
 #endif
 			/* Decompression is a lenghty process, so it's outside of the lock.
-			   This gives the isoc_handler the opportunity to fill more frames 
+			   This gives the isoc_handler the opportunity to fill more frames
 			   in the mean time.
 			*/
 			spin_unlock_irqrestore(&pdev->ptrlock, flags);
@@ -566,7 +570,7 @@
 }
 
 /**
-  \brief Advance pointers of image buffer (after each user request) 
+  \brief Advance pointers of image buffer (after each user request)
 */
 static inline void pwc_next_image(struct pwc_device *pdev)
 {
@@ -574,22 +578,6 @@
 	pdev->fill_image = (pdev->fill_image + 1) % default_mbufs;
 }
 
-/* 2001-10-14: YUV420P is the only palette remaining. */
-static int pwc_set_palette(struct pwc_device *pdev, int pal)
-{
-	if (   pal == VIDEO_PALETTE_YUV420P
-#if PWC_DEBUG
-            || pal == VIDEO_PALETTE_RAW
-#endif
-	) {
-		pdev->vpalette = pal;
-		pwc_set_image_buffer_size(pdev);
-		return 0;
-	}
-	Trace(TRACE_READ, "Palette %d not supported.\n", pal);
-	return -1;
-}
-
 
 
 /* This gets called for the Isochronous pipe (video). This is done in
@@ -601,14 +589,15 @@
 	int i, fst, flen;
 	int awake;
 	struct pwc_frame_buf *fbuf;
-	unsigned char *fillptr, *iso_buf;
+	unsigned char *fillptr = 0, *iso_buf = 0;
 
+	awake = 0;
 	pdev = (struct pwc_device *)urb->context;
 	if (pdev == NULL) {
 		Err("isoc_handler() called with NULL device?!\n");
 		return;
 	}
-#ifdef PWC_MAGIC	
+#ifdef PWC_MAGIC
 	if (pdev->magic != PWC_MAGIC) {
 		Err("isoc_handler() called with bad magic!\n");
 		return;
@@ -619,33 +608,51 @@
 		return;
 	}
 	if (urb->status != -EINPROGRESS && urb->status != 0) {
-		char *errmsg;
-		
+		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"; break;
+			case -EILSEQ:		errmsg = "CRC/Timeout (could be anything)"; break;
 			case -ETIMEDOUT:	errmsg = "NAK (device does not respond)"; break;
 		}
 		Trace(TRACE_FLOW, "pwc_isoc_handler() called with status %d [%s].\n", urb->status, errmsg);
-		return;
+		/* 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)
+		{
+			Info("Too many ISOC errors, bailing out.\n");
+			pdev->error_status = EIO;
+			awake = 1;
+		}
+		else
+			return; // better luck next time
 	}
 
 	fbuf = pdev->fill_frame;
 	if (fbuf == NULL) {
 		Err("pwc_isoc_handler without valid fill frame.\n");
+		awake = 1;
+	}
+	else {
+		fillptr = fbuf->data + fbuf->filled;
+	}
+	/* Premature wakeup */
+	if (awake) {
 		wake_up_interruptible(&pdev->frameq);
 		return;
 	}
-	fillptr = fbuf->data + fbuf->filled;
-	awake = 0;
+
+	/* 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 
+	          2 = synched
 	 */
 	/* Compact data */
 	for (i = 0; i < urb->number_of_packets; i++) {
@@ -674,7 +681,7 @@
 			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. 
+				   Decompression is done in user time over there.
 				 */
 				if (pdev->vsync == 2) {
 					/* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus 
@@ -731,7 +738,7 @@
 						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.
@@ -764,7 +771,7 @@
 			if (iso_error < 20)
 				Trace(TRACE_FLOW, "Iso frame %d of USB has error %d\n", i, fst);
 		}
-#endif			
+#endif
 	}
 	if (awake)
 		wake_up_interruptible(&pdev->frameq);
@@ -893,8 +900,10 @@
 		}
 	}
 
-	/* Stop camera, but only if we are sure the camera is still there */
-	if (!pdev->unplugged) {
+	/* Stop camera, but only if we are sure the camera is still there (unplug
+	   is signalled by EPIPE) 
+	 */
+	if (pdev->error_status && pdev->error_status != EPIPE) {
 		Trace(TRACE_OPEN, "Setting alternate interface 0.\n");
 		usb_set_interface(pdev->udev, 0, 0);
 	}
@@ -922,28 +931,6 @@
 }
 
 
-static inline void set_mem_leak(void *ptr)
-{
-	down(&mem_lock);
-	if (mem_leak != NULL)
-		Err("Memleak: overwriting mem_leak pointer!\n");
-	Trace(TRACE_MEMORY, "Setting mem_leak to 0x%p.\n", ptr);
-	mem_leak = ptr;
-	up(&mem_lock);
-}
-
-static inline void free_mem_leak(void)
-{
-	down(&mem_lock);
-	if (mem_leak != NULL) {
-		Trace(TRACE_MEMORY, "Freeing mem_leak ptr 0x%p.\n", mem_leak);
-		kfree(mem_leak);
-		mem_leak = NULL;
-	}
-	up(&mem_lock);
-}
-
-
 /***************************************************************************/
 /* Video4Linux functions */
 
@@ -967,8 +954,8 @@
 		Trace(TRACE_OPEN, "Doing first time initialization.\n");
 		pdev->usb_init = 1;
 		
-		if (pwc_trace & TRACE_OPEN) {
-			/* Query CMOS sensor type */
+		{
+			/* Query sensor type */
 			const char *sensor_type = NULL;
 
 			i = pwc_get_cmos_sensor(pdev);
@@ -987,7 +974,7 @@
 			default:    sensor_type = "unknown type of sensor"; break;
 			}
 			if (sensor_type != NULL)
-				Info("This %s camera is equipped with a %s (%d).\n", pdev->vdev->name, sensor_type, i);
+				Info("This %s camera is equipped with a %s (%d).\n", pdev->vdev.name, sensor_type, i);
 		}
 	}
 
@@ -1022,24 +1009,20 @@
 	pdev->vframe_count = 0;
 	pdev->vframes_dumped = 0;
 	pdev->vframes_error = 0;
-	pdev->vpalette = default_palette;
-#if PWC_DEBUG	
+	pdev->visoc_errors = 0;
+	pdev->error_status = 0;
+#if PWC_DEBUG
 	pdev->sequence = 0;
 #endif
 
 	/* Set some defaults */
 	pdev->vsnapshot = 0;
-	if (pdev->type == 730 || pdev->type == 740 || pdev->type == 750)
-		pdev->vsize = PSZ_QSIF;
-	else
-		pdev->vsize = PSZ_QCIF;
-	pdev->vframes = 10;
-
-	/* Start iso pipe for video; first try user-supplied size/fps, if
-	   that fails try QCIF/10 or QSIF/10 (a reasonable default), 
-	   then give up 
+
+	/* Start iso pipe for video; first try the last used video size
+	   (or the default one); if that fails try QCIF/10 or QSIF/10;
+	   it that fails too, give up.
 	 */
-	i = pwc_set_video_mode(pdev, pwc_image_sizes[default_size].x, pwc_image_sizes[default_size].y, default_fps, pdev->vcompression, 0);
+	i = pwc_set_video_mode(pdev, pwc_image_sizes[pdev->vsize].x, pwc_image_sizes[pdev->vsize].y, pdev->vframes, pdev->vcompression, 0);
 	if (i)	{
 		Trace(TRACE_OPEN, "First attempt at set_video_mode failed.\n");
 		if (pdev->type == 730 || pdev->type == 740 || pdev->type == 750)
@@ -1091,46 +1074,41 @@
 	if (pdev->vframe_count > 20)
 		Info("Closing video device: %d frames received, dumped %d frames, %d frames with errors.\n", pdev->vframe_count, pdev->vframes_dumped, pdev->vframes_error);
 
-	/* Free isoc URBs, stop camera */
+	if (pdev->decompressor != NULL) {
+		pdev->decompressor->exit();
+		pdev->decompressor->unlock();
+		pdev->decompressor = NULL;
+	}
+
 	pwc_isoc_cleanup(pdev);
+	pwc_free_buffers(pdev);
 
-	if (!pdev->unplugged) {
-		/* Turn LEDs off */
+	/* Turn off LEDS and power down camera, but only when not unplugged */
+	if (pdev->error_status != EPIPE) {
 		if (pwc_set_leds(pdev, 0, 0) < 0)
 			Info("Failed to set LED on/off time.\n");
-		/* Power down camera to save energy */
 		if (power_save) {
 			i = pwc_camera_power(pdev, 0);
 			if (i < 0) 
 				Err("Failed to power down camera (%d)\n", i);
 		}
 	}
-
 	pdev->vopen = 0;
-	if (pdev->decompressor != NULL) {
-		pdev->decompressor->exit();
-		pdev->decompressor->unlock();
-	}
-	pwc_free_buffers(pdev);
-
-	/* wake up _disconnect() routine */
-	if (pdev->unplugged)
-		wake_up(&pdev->remove_ok);
 	Trace(TRACE_OPEN, "<< video_close()\n");
 }
 
 /*
  *	FIXME: what about two parallel reads ????
  *      ANSWER: Not supported. You can't open the device more than once,
-                despite what the V4L1 interface says. First, I don't see 
-                the need, second there's no mechanism of alerting the 
+                despite what the V4L1 interface says. First, I don't see
+                the need, second there's no mechanism of alerting the
                 2nd/3rd/... process of events like changing image size.
-                And I don't see the point of blocking that for the 
+                And I don't see the point of blocking that for the
                 2nd/3rd/... process.
                 In multi-threaded environments reading parallel from any
                 device is tricky anyhow.
  */
- 
+
 static long pwc_video_read(struct video_device *vdev, char *buf, unsigned long count, int noblock)
 {
 	struct pwc_device *pdev;
@@ -1142,17 +1120,20 @@
 	pdev = vdev->priv;
 	if (pdev == NULL)
 		return -EFAULT;
-	if (pdev->unplugged) {
-		Info("pwc_video_read: Device got unplugged (1).\n");
-		return -EPIPE; /* unplugged device! */
-	}
+	if (pdev->error_status)
+		return -pdev->error_status; /* Something happened, report what. */
 
 	/* In case we're doing partial reads, we don't have to wait for a frame */
 	if (pdev->image_read_pos == 0) {
 		/* Do wait queueing according to the (doc)book */
 		add_wait_queue(&pdev->frameq, &wait);
-	        set_current_state(TASK_INTERRUPTIBLE);
 		while (pdev->full_frames == NULL) {
+			/* Check for unplugged/etc. here */
+			if (pdev->error_status) {
+				remove_wait_queue(&pdev->frameq, &wait);
+				set_current_state(TASK_RUNNING);
+				return -pdev->error_status ;
+			}
 	                if (noblock) {
 	                	remove_wait_queue(&pdev->frameq, &wait);
 	                	set_current_state(TASK_RUNNING);
@@ -1168,8 +1149,8 @@
 		}
 		remove_wait_queue(&pdev->frameq, &wait);
 		set_current_state(TASK_RUNNING);
-	                                                                                                                                                                                
-		/* Decompress [, convert] and release frame */
+
+		/* Decompress and release frame */
 		if (pwc_handle_frame(pdev))
 			return -EFAULT;
 	}
@@ -1191,35 +1172,33 @@
 
 static long pwc_video_write(struct video_device *vdev, const char *buf, unsigned long count, int noblock)
 {
-	return -EINVAL;   
+	return -EINVAL;
 }
 
 static unsigned int pwc_video_poll(struct video_device *vdev, struct file *file, poll_table *wait)
 {
 	struct pwc_device *pdev;
-	
+
 	if (vdev == NULL)
 		return -EFAULT;
 	pdev = vdev->priv;
 	if (pdev == NULL)
 		return -EFAULT;
-	
+
 	poll_wait(file, &pdev->frameq, wait);
-	if (pdev->unplugged) {
-		Info("pwc_video_poll: Device got unplugged.\n");
+	if (pdev->error_status)
 		return POLLERR;
-	}		
 	if (pdev->full_frames != NULL) /* we have frames waiting */
 		return (POLLIN | POLLRDNORM);
 
 	return 0;
 }
-        
+
 static int pwc_video_ioctl(struct video_device *vdev, unsigned int cmd, void *arg)
 {
 	struct pwc_device *pdev;
 	DECLARE_WAITQUEUE(wait, current);
-	
+
 	if (vdev == NULL)
 		return -EFAULT;
 	pdev = vdev->priv;
@@ -1228,7 +1207,7 @@
 
 	switch (cmd) {
 		/* Query cabapilities */
-		case VIDIOCGCAP: 
+		case VIDIOCGCAP:
 		{
 			struct video_capability caps;
 
@@ -1314,7 +1293,7 @@
 			else
 				p.colour = 0xffff;
 			p.depth = 24;
-			p.palette = pdev->vpalette;
+			p.palette = VIDEO_PALETTE_YUV420P;
 			p.hue = 0xFFFF; /* N/A */
 
 			if (copy_to_user(arg, &p, sizeof(p)))
@@ -1341,9 +1320,8 @@
 			pwc_set_contrast(pdev, p.contrast);
 			pwc_set_gamma(pdev, p.whiteness);
 			pwc_set_saturation(pdev, p.colour);
-			if (p.palette && p.palette != pdev->vpalette) {
-				if (pwc_set_palette(pdev, p.palette) < 0)
-					return -EINVAL;
+			if (p.palette && p.palette != VIDEO_PALETTE_YUV420P) {
+				return -EINVAL;
 			}
 			break;
 		}
@@ -1436,9 +1414,8 @@
 			   various palettes... The driver doesn't support
 			   such small images, so I'm working around it.
 			 */
-			if (vm.format && vm.format != pdev->vpalette)
-				if (pwc_set_palette(pdev, vm.format) < 0)
-					return -EINVAL;
+			if (vm.format && vm.format != VIDEO_PALETTE_YUV420P)
+				return -EINVAL;
 			 
 			if ((vm.width != pdev->view.x || vm.height != pdev->view.y) &&
 			    (vm.width >= pdev->view_min.x && vm.height >= pdev->view_min.y)) {
@@ -1458,7 +1435,7 @@
 			/* Okay, we're done here. In the SYNC call we wait until a 
 			   frame comes available, then expand image into the given 
 			   buffer.
-			   In contrast to the CPiA cam the Philips cams deliver a 
+			   In contrast to the CPiA cam the Philips cams deliver a
 			   constant stream, almost like a grabber card. Also,
 			   we have separate buffers for the rawdata and the image,
 			   meaning we can nearly always expand into the requested buffer.
@@ -1498,27 +1475,26 @@
 				return -EINVAL;
 
 			/* Add ourselves to the frame wait-queue.
-			   
+			   In the loop, check for error conditions and signals.
+
 			   FIXME: needs auditing for safety.
-			   QUSTION: In what respect? I think that using the
-			            frameq is safe now.
+			   QUESTION: In what respect? I think that using the
+			             frameq is safe now.
 			 */
 			add_wait_queue(&pdev->frameq, &wait);
-		        set_current_state(TASK_INTERRUPTIBLE);
 			while (pdev->full_frames == NULL) {
-				if (pdev->unplugged) {
+				if (pdev->error_status) {
 					remove_wait_queue(&pdev->frameq, &wait);
 					set_current_state(TASK_RUNNING);
-					return -ENODEV;
+					return -pdev->error_status;
 				}
-			
 	                	if (signal_pending(current)) {
 	                		remove_wait_queue(&pdev->frameq, &wait);
 		                	set_current_state(TASK_RUNNING);
 		                	return -ERESTARTSYS;
 	        	        }
-		                set_current_state(TASK_INTERRUPTIBLE);
 	                	schedule();
+		                set_current_state(TASK_INTERRUPTIBLE);
 			}
 			remove_wait_queue(&pdev->frameq, &wait);
 			set_current_state(TASK_RUNNING);
@@ -1572,7 +1548,7 @@
 		{
 			struct video_unit vu;
 			
-			vu.video = pdev->vdev->minor & 0x3F;
+			vu.video = pdev->vdev.minor & 0x3F;
 			vu.audio = -1; /* not known yet */
 			vu.vbi = -1;
 			vu.radio = -1;
@@ -1623,14 +1599,11 @@
 static void *usb_pwc_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
 {
 	struct pwc_device *pdev = NULL;
-	struct video_device *vdev;
 	int vendor_id, product_id, type_id;
 	int i, hint;
 	int video_nr = -1; /* default: use next available device */
 	char serial_number[30], *name;
 
-	free_mem_leak();
-	
 	/* Check if we can handle this device */
 	Trace(TRACE_PROBE, "probe() called [%04X %04X], if %d\n", udev->descriptor.idVendor, udev->descriptor.idProduct, ifnum);
 
@@ -1691,6 +1664,11 @@
 			name = "Philips 750 webcam";
 			type_id = 750;
 			break;
+		case 0x0313:
+			Info("Philips PCVC720K/40 (ToUCam XS) USB webcam detected.\n");
+			name = "Philips 720 webcam";
+			type_id = 720;
+			break;
 		default:
 			return NULL;
 			break;
@@ -1713,12 +1691,12 @@
 		case 0x08b0:
 			Info("Logitech QuickCam Pro 3000 USB webcam detected.\n");
 			name = "Logitech QuickCam Pro 3000";
-			type_id = 730;
+			type_id = 740; /* CCD sensor */
 			break;
 		case 0x08b1:
 			Info("Logitech QuickCam for Notebook Pro USB webcam detected.\n");
 			name = "Logitech QuickCam Notebook Pro";
-			type_id = 740; /* ?? unknown sensor */
+			type_id = 740; /* CCD sensor */
 			break;
 		case 0x08b2:
 			Info("Logitech QuickCam 4000 Pro USB webcam detected.\n");
@@ -1730,6 +1708,15 @@
 			name = "Logitech QuickCam Zoom";
 			type_id = 740; /* CCD sensor */
 			break;
+		case 0x08b4:
+		case 0x08b5:
+		case 0x08b6:
+		case 0x08b7:
+		case 0x08b8:
+			Info("Logitech QuickCam detected (reserved ID).\n");
+			name = "Logitech QuickCam (res.)";
+			type_id = 730; /* Assuming CMOS */
+			break;
         	default:
         		return NULL;
         		break;
@@ -1763,6 +1750,11 @@
 			name = "Creative Labs Webcam 5";
 			type_id = 730;
 			break;
+		case 0x4011:
+			Info("Creative Labs Webcam Pro Ex detected.\n");
+			name = "Creative Labs Webcam Pro Ex";
+			type_id = 740;
+			break;
 		default:
 			return NULL;
 			break;
@@ -1816,26 +1808,20 @@
 	memset(pdev, 0, sizeof(struct pwc_device));
 	pdev->type = type_id;
 	pwc_construct(pdev);
+	pdev->vsize = default_size;
+	pdev->vframes = default_fps;
 
 	init_MUTEX(&pdev->modlock);
 	pdev->ptrlock = SPIN_LOCK_UNLOCKED;
 
 	pdev->udev = udev;
 	init_waitqueue_head(&pdev->frameq);
-	init_waitqueue_head(&pdev->remove_ok);
 	pdev->vcompression = pwc_preferred_compression;
 
-	/* Now hook it up to the video subsystem */
-	vdev = kmalloc(sizeof(struct video_device), GFP_KERNEL);
-	if (vdev == NULL) {
-		Err("Oops, could not allocate memory for video_device.\n");
-		return NULL;
-	}
-	memcpy(vdev, &pwc_template, sizeof(pwc_template));
-	strcpy(vdev->name, name);
-	SET_MODULE_OWNER(vdev);
-	pdev->vdev = vdev;
-	vdev->priv = pdev;
+	memcpy(&pdev->vdev, &pwc_template, sizeof(pwc_template));
+	strcpy(pdev->vdev.name, name);
+	SET_MODULE_OWNER(&pdev->vdev);
+	pdev->vdev.priv = pdev;
 
 	pdev->release = udev->descriptor.bcdDevice;
 	Trace(TRACE_PROBE, "Release: %04x\n", pdev->release);
@@ -1854,14 +1840,14 @@
 		}
 	}
 
-	i = video_register_device(vdev, VFL_TYPE_GRABBER, video_nr);
+	i = video_register_device(&pdev->vdev, VFL_TYPE_GRABBER, video_nr);
 	if (i < 0) {
 		Err("Failed to register as video device (%d).\n", i);
+		kfree(pdev); /* Oops, no memory leaks please */
 		return NULL;
 	}
 	else {
-		Trace(TRACE_PROBE, "Registered video struct at 0x%p.\n", vdev);
-		Info("Registered as /dev/video%d.\n", vdev->minor & 0x3F);
+		Info("Registered as /dev/video%d.\n", pdev->vdev.minor & 0x3F);
 	}
 	/* occupy slot */
 	if (hint < MAX_DEV_HINTS) 
@@ -1876,75 +1862,56 @@
 {
 	struct pwc_device *pdev;
 	int hint;
-	DECLARE_WAITQUEUE(wait, current);
 
 	lock_kernel();
-	free_mem_leak();
-
 	pdev = (struct pwc_device *)ptr;
 	if (pdev == NULL) {
 		Err("pwc_disconnect() Called without private pointer.\n");
+		unlock_kernel();
 		return;
 	}
 	if (pdev->udev == NULL) {
 		Err("pwc_disconnect() already called for %p\n", pdev);
+		unlock_kernel();
 		return;
 	}
 	if (pdev->udev != udev) {
 		Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n");
+		unlock_kernel();
 		return;
 	}
-#ifdef PWC_MAGIC	
+#ifdef PWC_MAGIC
 	if (pdev->magic != PWC_MAGIC) {
 		Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n");
+		unlock_kernel();
 		return;
 	}
 #endif	
-	
-	pdev->unplugged = 1;
-	if (pdev->vdev != NULL) {
-		add_wait_queue(&pdev->remove_ok, &wait);
-		set_current_state(TASK_UNINTERRUPTIBLE);
-		Trace(TRACE_PROBE, "Unregistering video device.\n");
-		video_unregister_device(pdev->vdev); 
-		if (pdev->vopen) {
-			Info("Disconnected while device/video is open!\n");
-			
-			/* Wake up any processes that might be waiting for
-			   a frame, let them return an error condition
-			 */
-			wake_up(&pdev->frameq);
-			
-			/* Wait until we get a 'go' from _close(). This used
-			   to have a gigantic race condition, since we kfree()
-			   stuff here, but we have to wait until close() 
-			   is finished. 
-			 */
-			   
-			Trace(TRACE_PROBE, "Sleeping on remove_ok.\n");
-			/* ... wait ... */
-			schedule();
-			Trace(TRACE_PROBE, "Done sleeping.\n");
-			set_mem_leak(pdev->vdev);
-			pdev->vdev = NULL;
-		}
-		else {
-			/* Normal disconnect; remove from available devices */
-			kfree(pdev->vdev);
-			pdev->vdev = NULL;
-		}
-		remove_wait_queue(&pdev->remove_ok, &wait);
-		set_current_state(TASK_RUNNING);
+
+	/* We got unplugged; this is signalled by an EPIPE error code */
+	if (pdev->vopen) {
+		Info("Disconnected while webcam is in use!\n");
+		pdev->error_status = EPIPE;
 	}
+	
+	/* Alert waiting processes */
+	wake_up_interruptible(&pdev->frameq);
+	/* Wait until device is closed */
+	while (pdev->vopen)
+		schedule();
+	/* Device is now closed, so we can safely unregister it */
+	Trace(TRACE_PROBE, "Unregistering video device in disconnect().\n");
+	video_unregister_device(&pdev->vdev); 
+
+	/* Free memory (don't set pdev to 0 just yet) */
+	kfree(pdev);
 
 	/* search device_hint[] table if we occupy a slot, by any chance */
 	for (hint = 0; hint < MAX_DEV_HINTS; hint++)
 		if (device_hint[hint].pdev == pdev)
 			device_hint[hint].pdev = NULL;
 
-	pdev->udev = NULL;
 	unlock_kernel();
-	kfree(pdev);
 }
 
 
@@ -1973,7 +1940,7 @@
 static int trace = -1;
 static int compression = -1;
 static int leds[2] = { -1, -1 };
-static char *dev_hint[10] = { };
+static char *dev_hint[MAX_DEV_HINTS] = { };
 
 MODULE_PARM(size, "s");
 MODULE_PARM_DESC(size, "Initial image size. One of sqcif, qsif, qcif, sif, cif, vga");
@@ -1991,7 +1958,7 @@
 MODULE_PARM_DESC(compression, "Preferred compression quality. Range 0 (uncompressed) to 3 (high compression)");
 MODULE_PARM(leds, "2i");
 MODULE_PARM_DESC(leds, "LED on,off time in milliseconds");
-MODULE_PARM(dev_hint, "0-10s");
+MODULE_PARM(dev_hint, "0-20s");
 MODULE_PARM_DESC(dev_hint, "Device node hints");
 
 MODULE_DESCRIPTION("Philips USB & OEM webcam driver");
@@ -2133,14 +2100,12 @@
 			device_hint[i].type = 0; /* not filled */
 	} /* ..for MAX_DEV_HINTS */
 
-	init_MUTEX(&mem_lock);
  	Trace(TRACE_PROBE, "Registering driver at address 0x%p.\n", &pwc_driver);
 	return usb_register(&pwc_driver);
 }
 
 static void __exit usb_pwc_exit(void)
 {
-	free_mem_leak();
 	Trace(TRACE_MODULE, "Deregistering driver.\n");
 	usb_deregister(&pwc_driver);
 	Info("Philips webcam module removed.\n");

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)